黑马程序员:我对多线程的总结以及练习(重点)

来源:互联网 发布:软件敏捷开发模式 编辑:程序博客网 时间:2024/05/16 17:51
1.概述进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。一个进程中至少有一个线程。线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。多线程存在的意义:充分利用cpu的空闲时间,提高进程的整体运行效率。 2.创建线程的两种方式第一种:继承Thread类步骤:    1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。    2.建立子类对象的同时线程也被创建。    3.通过调用start方法开启线程。示例代码:[java] view plaincopy1.class Demo extends Thread  2.{  3.    public void run()  4.    {  5.        for(int i = 0;i<60;i++)  6.        {  7.            System.out.println(“demo run---+”i);  8.        }  9.    }  10.}  11.  12.class ThreadDemo  13.{  14.    public static void main(String[] args)  15.    {  16.            Demo d = new Demo();//创建一个线程  17.            d.start();//调用start方法执行该线程的run方法  18.    }  19.}  第二种:实现Runnable接口步骤:    1.子类覆盖接口中的run方法    2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。    3.Thread类对象调用start方法开启线程。代码示例:[java] view plaincopy1.class Test implement Runnable  2.{  3.    public void run()//子类覆盖接口中的run方法  4.    {  5.        for(int x=0; x<60; x++)  6.        {  7.            System.out.println(“run..."+x);  8.        }  9.    }  10.  11.}  12.  13.  14.class ThreadTest   15.{  16.    public static void main(String[] args)   17.    {  18.        Test te = new Test();实现Runnable接口的对象  19.        Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数  20.        Thread t2 = new Thread(te);  21.        t1.start();//调用Thread类的start方法开启线程  22.        t2.start();  23.    }  24.}  两种线程创建方式的区别:    继承Thread类创建对象:        1.Thread子类无法再从其它类继承(java语言单继承)。        2.编写简单,run()方法的当前对象就是线程对象,可直接操作。    使用Runnable接口创建线程:        1.可以将CPU,代码和数据分开,形成清晰的模型。        2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。        3.有利于保持程序的设计风格一致。    继承Thread类线程代码存放于Thread子类run方法中;    实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。 问题:为什么要将Runnable接口的子类对象传递给Thread的构造函数?答:因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。 3.线程的四种状态sleep方法需要指定睡眠时间,单位是毫秒。冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。 4.线程的对象获取与名称的操作线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。操作方法:static Thread currentThread():获取当前线程对象。getName(): 获取线程名称。setName或者构造函数:设置线程名称。示例代码: [java] view plaincopy1.class Test extends Thread  2.{  3.    Test(String name)//构造方法设置线程名称  4.    {  5.        super(name);//继承父类方法  6.    }  7.    public void run()//执行代码  8.    {  9.        for(int x=0; x<60; x++)  10.        {  11.            System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);  12.        }  13.    }  14.  15.}  16.  17.class ThreadTest   18.{  19.    public static void main(String[] args)   20.    {  21.        Test t1 = new Test("one---");//调用构造函数设置线程名称  22.        Test t2 = new Test("two+++");  23.        t1.start();  24.        t2.start();  25.  26.        for(int x=0; x<60; x++)  27.        {  28.            System.out.println("main....."+x);  29.        }  30.    }  31.}   5.线程的安全导致安全问题的出现的原因:    1.多个线程访问出现延迟    2.线程随机性线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的解决方法:同步(synchronized)格式:    synchronized(对象)    {        需要同步的代码;    }同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。4个窗口售票示例:[java] view plaincopy1.class Ticket implements Runnable  2.{  3.    private  int tick = 1000;  4.    Object obj = new Object();//建立一个obj类对象,供synchronized作参数  5.    public void run()  6.    {  7.        while(true)  8.        {  9.            synchronized(obj)//同步锁  10.            {  11.                if(tick>0)  12.                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  13.  14.            }  15.        }  16.    }  17.}  18.  19.class  TicketDemo2  20.{  21.    public static void main(String[] args)   22.    {  23.  24.        Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用  25.  26.        Thread t1 = new Thread(t);  27.        Thread t2 = new Thread(t);  28.        Thread t3 = new Thread(t);  29.        Thread t4 = new Thread(t);  30.        //开始4个线程  31.        t1.start();  32.        t2.start();  33.        t3.start();  34.        t4.start();  35.  36.    }  37.}  同步的前提:|---->同步需要两个或者两个以上的线程|---->多个线程使用的是同一个锁未满足这两个条件,不能称其同步。同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。同步函数    格式:在函数上加上synchronized修饰符即可。    同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。   所以同步函数使用的锁是this。关于死锁    同步中嵌套同步,但是锁却不同,就会产生死锁 如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁[java] view plaincopy1.class Dead implements Runnable  2.{  3.    private booleanb = false;  4.    Dead(booleanb)  5.    {  6.        this.b = b;  7.    }  8.    public void run()  9.    {  10.        while(true)  11.        {  12.            if(b)  13.            {  14.                synchronized(Locks.locka)  15.                {//0线程,持有了A锁  16.                    System.out.println(Thread.currentThread().getName()+"locka...+..if");  17.                    //等待B锁  18.                    synchronized(Locks.lockb)  19.                    {  20.                        System.out.println(Thread.currentThread().getName()+"lockb...+..if");  21.                    }  22.                }  23.            }  24.            else  25.            {  26.                synchronized(Locks.lockb)  27.                {  28.                    //1线程就进来了,持有了B锁  29.                    System.out.println(Thread.currentThread().getName()+"lockb...+..else");  30.                    synchronized(Locks.locka)//等待获得A锁  31.                    {  32.                        System.out.println(Thread.currentThread().getName()+"locka...+..else");  33.  34.                    }  35.                }  36.            }  37.        }  38.    }  39.}  40.//创造锁  41.class Locks  42.{  43.    public static Object locka = new Object();  44.    public static Object lockb = new Object();  45.}  46.class DeadLock  47.{  48.    public static void main(String[]args)  49.    {  50.        Dead d1 = new Dead(true);  51.        Dead d2 = new Dead(false);  52.        Thread t1 = new Thread(d1);  53.        Thread t2 = new Thread(d2);  54.        t1.start();  55.        t2.start();  56.    }  57.}   6.线程间通信多个线程操作同一个资源,但是操作的动作不同 注意:wait();notify();notifyAll()都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,因为只有同步才具有锁。问题:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?    1.这些方法存在与同步中。    2.使用这些方法时必须要标识所属的同步的锁。    3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。注意:不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。问题:wait(),sleep()有什么区别?    wait():释放cpu执行权,释放锁。    sleep():释放cpu执行权,不释放锁。 新特性:JDK1.5之后新增了java.until.concurrent.locks这个包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。生产和消费实例中升级:JDK1.5 中提供了多线程升级解决方案。    将同步Synchronized替换成现实Lock操作。    将Object中的wait,notify notifyAll,替换了Condition对象。    该对象可以Lock锁 进行获取。    该示例中,实现了本方只唤醒对方操作。代码如下:[java] view plaincopy1.import java.util.concurrent.locks.*;  2.  3.class ProducerConsumerDemo   4.{  5.    public static void main(String[] args)   6.    {  7.        Resource r = new Resource();  8.  9.        Producer pro = new Producer(r);  10.        Consumer con = new Consumer(r);  11.  12.        Thread t1 = new Thread(pro);  13.        Thread t2 = new Thread(pro);  14.        Thread t3 = new Thread(con);  15.        Thread t4 = new Thread(con);  16.  17.        t1.start();  18.        t2.start();  19.        t3.start();  20.        t4.start();  21.  22.    }  23.}  24.  25.class Resource  26.{  27.    private String name;  28.    private int count = 1;  29.    private boolean flag = false;  30.    private Lock lock = new ReentrantLock();  31.  32.    private Condition condition_pro = lock.newCondition();  33.    private Condition condition_con = lock.newCondition();  34.  35.  36.    public  void set(String name)throws InterruptedException  37.    {  38.        lock.lock();  39.        try  40.        {  41.            while(flag)  42.                condition_pro.await();  43.            this.name = name+"--"+count++;  44.  45.            System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);  46.            flag = true;  47.            condition_con.signal();  48.        }  49.        finally  50.        {  51.            lock.unlock();//释放锁的动作一定要执行。  52.        }  53.    }  54.    public  void out()throws InterruptedException  55.    {  56.        lock.lock();  57.        try  58.        {  59.            while(!flag)  60.                condition_con.await();  61.            System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);  62.            flag = false;  63.            condition_pro.signal();  64.        }  65.        finally  66.        {  67.            lock.unlock();  68.        }  69.          70.    }  71.}  72.  73.class Producer implements Runnable  74.{  75.    private Resource res;  76.  77.    Producer(Resource res)  78.    {  79.        this.res = res;  80.    }  81.    public void run()  82.    {  83.        while(true)  84.        {  85.            try  86.            {  87.                res.set("+商品+");  88.            }  89.            catch (InterruptedException e)  90.            {  91.            }  92.              93.        }  94.    }  95.}  96.  97.class Consumer implements Runnable  98.{  99.    private Resource res;  100.  101.    Consumer(Resource res)  102.    {  103.        this.res = res;  104.    }  105.    public void run()  106.    {  107.        while(true)  108.        {  109.            try  110.            {  111.                res.out();  112.            }  113.            catch (InterruptedException e)  114.            {  115.            }  116.        }  117.    }  118.}   7.停止线程1.定义循环结束标记(run方法结束)    因为线程运行代码一般都是循环,只要控制了循环即可。2.使用interrupt(中断)方法。    该方法是结束线程的冻结状态,使线程回到运行状态中来。注:stop方法已经过时不再使用。 特殊情况:    当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。    Thread类提供该方法 interrupt();测试代码:[java] view plaincopy1.class StopThread implements Runnable  2.{  3.    private boolean flag =true;  4.    public synchornized void run()  5.    {  6.        while(flag)  7.        {  8.            try  9.            {  10.                wait();//调用线程等待  11.            }  12.            catch(InterruptedException e)  13.            {  14.                System.out.println(Thread.currentThread().getName()+"...Exception");  15.                flag = false;//在异常中控制线程的运行  16.            }  17.            System.out.println(Thread.currentThread().getName()+"....run");  18.        }  19.    }  20.    public void changeFlag()  21.    {  22.        flag = false;  23.    }  24.}  25.  26.class  StopThreadDemo  27.{  28.    public static void main(String[] args)   29.    {  30.        StopThread st = new StopThread();  31.          32.        Thread t1 = new Thread(st);  33.        Thread t2 = new Thread(st);  34.  35.  36.        t1.setDaemon(true);  37.        t2.setDaemon(true);  38.        t1.start();  39.        t2.start();  40.  41.        int num = 0;  42.  43.        while(true)  44.        {  45.            if(num++ == 60)  46.            {  47.                //st.changeFlag();  48.                t1.interrupt();//调用interrupt方法来中断线程,抛出异常  49.                t2.interrupt();  50.                break;  51.            }  52.            System.out.println(Thread.currentThread().getName()+"......."+num);  53.        }  54.        System.out.println("over");  55.    }  56.}  8.线程类的其他方法守护线程    setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。    注意:该方法必须在启动线程前调用。           该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。用法示例代码如下:[java] view plaincopy1.class StopThread implements Runnable  2.{  3.    public  void run()//原本run方法是一个死循环,但是将线程定义为守护线程后,主线程结束,访问run方法的线程立即也结束了  4.    {  5.        while(true)  6.        {         7.            System.out.println(Thread.currentThread().getName()+"....run");  8.        }  9.    }  10.}  11.class  StopThreadDemo  12.{  13.    public static void main(String[] args)   14.    {  15.        StopThread st = new StopThread();     16.        Thread t1 = new Thread(st);  17.        Thread t2 = new Thread(st);  18.        //将t1,t2定义为守护线程  19.        t1.setDaemon(true);  20.        t2.setDaemon(true);  21.        t1.start();//开始线程  22.        t2.start();  23.        int num = 0;  24.        while(true)  25.        {  26.            if(num++ == 60)  27.            {  28.                break;  29.            }  30.            System.out.println(Thread.currentThread().getName()+"......."+num);  31.        }  32.        System.out.println("over");//主线程结束,t1,t2也结束  33.    }  34.}  join()抢夺cpu执行权,适用于临时加入线程用,先执行完,其他线程才执行。测试代码如下:[java] view plaincopy1.class Demo implements Runnable  2.{  3.    public void run()  4.    {  5.        for(int x=0; x<70; x++)  6.        {  7.            System.out.println(Thread.currentThread().getName()+"....."+x);  8.        }  9.    }  10.}  11.class  JoinDemo  12.{  13.    public static void main(String[] args) throws Exception  14.    {  15.        Demo d = new Demo();  16.        Thread t1 = new Thread(d);  17.        Thread t2 = new Thread(d);  18.        t1.start();  19.        t1.join();//t1获取cpu的执行权,主线程处于冻结状态,只有t1结束主线程才能恢复运行状态  20.        t2.start();  21.        //t1.join();主线程冻结,t1,t2交替运行,t1结束,主线程才继续  22.        for(int x=0; x<80; x++)  23.        {       }  24.        System.out.println("over");  25.    }  26.}  setPriority()更改线程的优先级。三个优先级分别为:    MAX_PRIORITY(最高优先级,10)    MIN_PRIORITY(最低优先级,1)    NORM_PRIORITY(默认优先级,5)首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。 yeild()暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行) 个人总结:    线程的运行状态与线程之间的通信个人很难理解,可能还得多多写代码操练才行,线程的操作给我的感觉就是你必须要考虑得很全面,每个线程的执行状态,每个线程读取的数据什么的都必须要考虑清楚,这要才能避免线程的安全问题。还是觉得线程这一块比较难,得多下点工夫。

0 0
原创粉丝点击