A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始







5、多线程

5.3 线程间通信

    5.3.1 线程间通信涉及的方法

    多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

    等待/唤醒机制涉及的方法:
    1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
    2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
    3. notifyAll():唤醒线程池中的所有线程。


    P.S.
    1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
    2、必须要明确到底操作的是哪个锁上的线程!
    3、wait和sleep区别?
         1)wait可以指定时间也可以不指定。sleep必须指定时间。
         2)在同步中时,对CPU的执行权和锁的处理不同。
         wait:释放执行权,释放锁。
         sleep:释放执行权,不释放锁。


    为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在object类中。


    生产者-消费者问题:
  1. class Resource{
  2.        private String name ;
  3.        private String sex ;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name,String sex){
  6.              if(flag )
  7.                    try{
  8.                          this.wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name;
  13.              this.sex = sex;
  14.              flag = true ;
  15.              this.notify();
  16.       }
  17.       
  18.       public synchronized void out(){
  19.              if(!flag )
  20.                    try{
  21.                          this.wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.              System. out.println(name + "..." + sex);
  26.              flag = false ;
  27.              this.notify();
  28.      }
  29. }

  30. //输入
  31. class Input implements Runnable{
  32.       Resource r;
  33.       Input(Resource r){
  34.              this.r = r;
  35.       }

  36.       public void run(){
  37.              int x = 0;
  38.              while(true ){
  39.                    if(x == 0){
  40.                          r.set( "mike","男" );
  41.                   } else{
  42.                          r.set( "lili","女" );
  43.                   }
  44.                   x = (x + 1)%2;
  45.             }
  46.       }
  47. }

  48. //输出
  49. class Output implements Runnable{
  50.       Resource r;

  51.       Output(Resource r){
  52.              this.r = r;
  53.       }

  54.        public void run(){
  55.              while(true ){
  56.                    r.out();
  57.             }
  58.       }
  59. }

  60. class ResourceDemo {
  61.        public static void main(String[] args){
  62.              //创建资源
  63.             Resource r = new Resource();
  64.              //创建任务
  65.             Input in = new Input(r);
  66.             Output out = new Output(r);
  67.              //创建线程,执行路径
  68.             Thread t1 = new Thread(in);
  69.             Thread t2 = new Thread(out);
  70.              //开启线程
  71.             t1.start();
  72.             t2.start();
  73.       }
  74. }
复制代码
   运行结果:

    多生产者-多消费者问题:
  1. class Resource{
  2.        private String name ;
  3.        private int count = 1;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name){
  6.              if(flag )
  7.                    try{
  8.                         wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name + count;
  13.              count++;
  14.              System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  15.              flag = true ;
  16.             notify();
  17.        }

  18.        public synchronized void out(){
  19.             if(!flag )
  20.                    try{
  21.                         wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.             flag = false ;
  26.             notify();
  27.             System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  28.       }
  29. }

  30. class Producer implements Runnable{
  31.       private Resource r ;
  32.       Producer(Resource r){
  33.              this.r = r;
  34.       }
  35.        public void run(){
  36.              while(true ){
  37.                    r.set( "烤鸭");
  38.             }
  39.       }
  40. }

  41. class Consumer implements Runnable{
  42.       private Resource r ;
  43.       Consumer(Resource r){
  44.              this.r = r;
  45.       }
  46.        public void run(){
  47.              while(true ){
  48.                    r.out();
  49.             }
  50.       }
  51. }

  52. class ProducerConsumerDemo {
  53.        public static void main(String[] args){
  54.             Resource r = new Resource();
  55.             Producer pro = new Producer(r);
  56.             Consumer con = new Consumer(r);

  57.             Thread t0 = new Thread(pro);
  58.             Thread t1 = new Thread(pro);
  59.             Thread t2 = new Thread(con);
  60.             Thread t3 = new Thread(con);
  61.             t0.start();
  62.             t1.start();
  63.             t2.start();
  64.             t3.start();
  65.       }
  66. }
复制代码
   
运行结果:以上代码存在安全问题。  

    原因分析:
    得到以上结果的过程分析如下:
    1. 线程Thread-0获取到CPU执行权及锁,生产了烤鸭3298,将flag设置为true。然后,Thread-0又重新获取到CPU执行权,由于flag为true,故执行wait方法,阻塞。Thread-1接着获取到CPU执行权,由于flag为true,故执行wait方法,也阻塞。


    2. 线程Thread-3获取到CPU执行权及锁,消费了烤鸭3298,将flag设置为false。然后,线程Thread-0被唤醒,但是并没有获取到锁,而是线程Thread-3接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-3阻塞。下面线程Thread-2接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-2也阻塞。


    3. 线程Thread-0获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3299,然后又唤醒线程Thread-1获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3300。从而造成了烤鸭3299还没有被消费,就直接生产了烤鸭3300的情况。

    由于if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断标记,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。
    notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。

    P.S.
    while判断标记+notify会导致死锁的示例:
    如果将上面的代码中的if判断标记修改成wile判断标记,就会出现死锁的现象,前2步与原来是一致的。第3步如下:
    3. 线程Thread-0获取到CPU执行权及锁,通过了while语句判断,直接生产烤鸭3299,将flag设置为true。然后又唤醒线程Thread-1获取到CPU执行权及锁,没有通过while语句判断,阻塞。线程Thread-0又获取到CPU执行权及锁,通不过while语句判断,也阻塞,此时Thread-0、1、2、3都阻塞,故死锁。

    代码:
  1. class Resource{
  2.        private String name ;
  3.        private int count = 1;
  4.        private boolean flag = false;

  5.        public synchronized void set(String name){
  6.             while(flag )
  7.                    try{
  8.                          this.wait();
  9.                   } catch(InterruptedException e){
  10.                         e.printStackTrace();
  11.                   }
  12.              this.name = name + count;
  13.              count++;
  14.              System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  15.              flag = true ;
  16.              notifyAll();
  17.       }

  18.        public synchronized void out(){
  19.              while(!flag )
  20.                    try{
  21.                          this.wait();
  22.                   } catch(InterruptedException e){
  23.                         e.printStackTrace();
  24.                   }
  25.             flag = false ;
  26.             notifyAll();
  27.             System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  28.       }
  29. }

  30. class Producer implements Runnable{
  31.        private Resource r ;
  32.       Producer(Resource r){
  33.              this.r = r;
  34.       }
  35.        public void run(){
  36.              while(true ){
  37.                    r.set( "烤鸭");
  38.             }
  39.       }
  40. }

  41. class Consumer implements Runnable{
  42.        private Resource r ;
  43.       Consumer(Resource r){
  44.              this.r = r;
  45.       }
  46.        public void run(){
  47.              while(true ){
  48.                    r.out();
  49.             }
  50.       }
  51. }

  52. class ProducerConsumerDemo {
  53.        public static void main(String[] args){
  54.             Resource r = new Resource();
  55.             Producer pro = new Producer(r);
  56.             Consumer con = new Consumer(r);

  57.             Thread t0 = new Thread(pro);
  58.             Thread t1 = new Thread(pro);
  59.             Thread t2 = new Thread(con);
  60.             Thread t3 = new Thread(con);
  61.             t0.start();
  62.             t1.start();
  63.             t2.start();
  64.             t3.start();
  65.       }
  66. }
复制代码
    运行结果:

    5.3.2 JDK1.5新特性

    同步代码块就是对于锁的操作是隐式的。
    JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

    Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
    lock():获取锁。
    unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。

    Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
    Condition接口中的await方法对应于Object中的wait方法。
    Condition接口中的signal方法对应于Object中的notify方法。
    Condition接口中的signalAll方法对应于Object中的notifyAll方法。

    使用一个Lock、一个Condition修改上面的多生产者-多消费者问题。

    代码:
  1. import java.util.concurrent.locks.*;
  2. class Resource{
  3.        private String name ;
  4.        private int count = 1;
  5.        private boolean flag = false;
  6.       
  7.       //创建一个锁对象
  8.       Lock lock = new ReentrantLock();

  9.        //通过已有的锁获取该锁上的监视器对象      
  10.       Condition con = lock .newCondition();

  11.        public void set(String name){
  12.              lock.lock();
  13.              try{
  14.                    while(flag )
  15.                          try{
  16.                               con.await();
  17.                         } catch(InterruptedException e){
  18.                               e.printStackTrace();
  19.                         }
  20.                    this.name = name + count;
  21.                    count++;
  22.                    System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  23.                    flag = true ;
  24.                    con.signalAll();
  25.             }finally{
  26.                    lock.unlock();
  27.             }
  28.       }

  29.        public void out(){
  30.             lock.lock();
  31.              try{
  32.                    while(!flag )
  33.                          try{
  34.                               con.await();
  35.                         } catch(InterruptedException e){
  36.                               e.printStackTrace();
  37.                         }
  38.                    flag = false ;
  39.                    con.signalAll();
  40.                    System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  41.             }finally{
  42.                    lock.unlock();
  43.             }
  44.       }
  45. }

  46. class Producer implements Runnable{
  47.        private Resource r ;
  48.        Producer(Resource r){
  49.              this.r = r;
  50.        }
  51.        public void run(){
  52.              while(true ){
  53.                    r.set( "烤鸭");
  54.             }
  55.       }
  56. }

  57. class Consumer implements Runnable{
  58.        private Resource r ;
  59.        Consumer(Resource r){
  60.              this.r = r;
  61.        }
  62.        public void run(){
  63.              while(true ){
  64.                    r.out();
  65.             }
  66.       }
  67. }

  68. class ProducerConsumerDemo {
  69.        public static void main(String[] args){
  70.             Resource r = new Resource();
  71.             Producer pro = new Producer(r);
  72.             Consumer con = new Consumer(r);

  73.             Thread t0 = new Thread(pro);
  74.             Thread t1 = new Thread(pro);
  75.             Thread t2 = new Thread(con);
  76.             Thread t3 = new Thread(con);
  77.             t0.start();
  78.             t1.start();
  79.             t2.start();
  80.             t3.start();
  81.       }
  82. }
复制代码
   运行结果:


    使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。

    代码:
  1. import java.util.concurrent.locks.*;
  2. class Resource{
  3.        private String name ;
  4.        private int count = 1;
  5.        private boolean flag = false;
  6.       
  7.        //创建一个锁对象
  8.        Lock lock = new ReentrantLock();

  9.        //通过已有的锁获取该锁上的监视器对象      
  10.        Condition con = lock .newCondition();

  11.        //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
  12.        Condition producer_con = lock .newCondition();
  13.        Condition consumer_con = lock .newCondition();

  14.        public void set(String name){
  15.              lock.lock();
  16.              try{
  17.                    while(flag )
  18.                          try{
  19.                               producer_con.await();
  20.                         } catch(InterruptedException e){
  21.                               e.printStackTrace();
  22.                         }
  23.                    this.name = name + count;
  24.                    count++;
  25.                    System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
  26.                    flag = true ;
  27.                    consumer_con.signal();
  28.             } finally{
  29.                    lock.unlock();
  30.             }
  31.       }

  32.        public void out(){
  33.              lock.lock();
  34.              try{
  35.                    while(!flag )
  36.                          try{
  37.                                consumer_con.await();
  38.                         } catch(InterruptedException e){
  39.                               e.printStackTrace();
  40.                         }
  41.                    flag = false ;
  42.                    producer_con.signal();
  43.                    System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
  44.             } finally{
  45.                    lock.unlock();
  46.             }
  47.       }
  48. }

  49. class Producer implements Runnable{
  50.        private Resource r ;
  51.        Producer(Resource r){
  52.              this.r = r;
  53.        }
  54.        public void run(){
  55.              while(true ){
  56.                    r.set( "烤鸭");
  57.             }
  58.       }
  59. }

  60. class Consumer implements Runnable{
  61.        private Resource r ;
  62.        Consumer(Resource r){
  63.              this.r = r;
  64.        }
  65.        public void run(){
  66.              while(true ){
  67.                    r.out();
  68.             }
  69.       }
  70. }

  71. class ProducerConsumerDemo {
  72.        public static void main(String[] args){
  73.             Resource r = new Resource();
  74.             Producer pro = new Producer(r);
  75.             Consumer con = new Consumer(r);

  76.             Thread t0 = new Thread(pro);
  77.             Thread t1 = new Thread(pro);
  78.             Thread t2 = new Thread(con);
  79.             Thread t3 = new Thread(con);
  80.             t0.start();
  81.             t1.start();
  82.             t2.start();
  83.             t3.start();
  84.       }
  85. }
复制代码
   运行结果:


    利用数组解决生产者-消费者问题。

    代码:
  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;

  4. class BoundedBuffer {
  5.    final Lock lock = new ReentrantLock();
  6.    final Condition notFull  = lock .newCondition();
  7.    final Condition notEmpty = lock .newCondition();

  8.    final Object[] items = new Object[100];
  9.    int putptr, takeptr , count ;

  10.    public void put(Object x) throws InterruptedException {
  11.      lock.lock();
  12.      try {
  13.        while (count == items.length)
  14.          notFull.await();
  15.          items[putptr ] = x;
  16.          if (++putptr == items.length) putptr = 0;
  17.          ++ count;
  18.          notEmpty.signal();
  19.       } finally {
  20.         lock.unlock();
  21.      }
  22.    }

  23.    public Object take() throws InterruptedException {
  24.       lock.lock();
  25.       try {
  26.        while (count == 0)
  27.          notEmpty.await();
  28.        Object x = items[takeptr];
  29.        if (++takeptr == items.length) takeptr = 0;
  30.        -- count;
  31.        notFull.signal();
  32.        return x;
  33.      } finally {
  34.        lock.unlock();
  35.      }
  36.    }
复制代码

    5.3.3 停止线程  

    怎么控制线程的任务结束呢?
    任务中都会有循环结构,只要控制住循环就可以结束任务。
    控制循环通常就用定义标记来完成。

    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public void run(){
  4.              while(flag ){
  5.                   System. out.println(Thread.currentThread().getName() + "...");
  6.             }
  7.        }
  8.        public void setFlag(){
  9.              flag = false ;
  10.        }
  11. }

  12. class StopThreadDemo{
  13.        public static void main(String[] args){
  14.             StopThread st = new StopThread();

  15.             Thread t1 = new Thread(st);
  16.             Thread t2 = new Thread(st);

  17.             t1.start();
  18.             t2.start();

  19.              int num = 1;
  20.              for(;;){
  21.                    if(++num == 50){
  22.                         st.setFlag();
  23.                          break;
  24.                   }
  25.                   System. out.println("main..." + num);
  26.              }
  27.              System. out.println("over" );
  28.       }
  29. }
复制代码
   运行结果:
……


    P.S.
    也可以使用stop方法停止线程,不过已经过时,不再使用。

    但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
    可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。强制动作会发生InterruptedException,一定要记得处理。


    API文档叙述如下:


    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public synchronized void run(){
  4.              while(flag){
  5.                    try{
  6.                         wait();
  7.                   } catch(InterruptedException e){
  8.                         System.out.println(Thread.currentThread().getName() + "..." + e);
  9.                         flag = false;
  10.                   }
  11.                   System.out.println(Thread.currentThread().getName() + "......");
  12.             }
  13.       }
  14.        public void setFlag(){
  15.             flag = false;
  16.       }
  17. }

  18. class StopThreadDemo{
  19.        public static void main(String[] args){
  20.             StopThread st = new StopThread();

  21.             Thread t1 = new Thread(st);
  22.             Thread t2 = new Thread(st);

  23.             t1.start();
  24.             t2.start();

  25.              int num = 1;
  26.              for(;;){
  27.                    if(++num == 50){
  28.                         t1.interrupt();
  29.                         t2.interrupt();
  30.                          break;
  31.                   }
  32.                   System.out.println( "main..." + num);
  33.             }
  34.             System.out.println( "over");
  35.       }
  36. }
复制代码
   运行结果:
……


    5.3.4 线程类的其他方法

    setDaemon方法:


    示例:
  1. class StopThread implements Runnable{
  2.        private boolean flag = true;
  3.        public synchronid void run(){
  4.              while(flag ){
  5.                    try{
  6.                         wait();
  7.                   } catch(InterruptedException e){
  8.                         System. out.println(Thread.currentThread().getName() + "..." + e);
  9.                          flag = false ;
  10.                   }
  11.                   System. out.println(Thread.currentThread().getName() + "......");
  12.             }
  13.       }
  14.        public void setFlag(){
  15.              flag = false ;
  16.       }
  17. }

  18. class StopThreadDemo{
  19.        public static void main(String[] args){
  20.             StopThread st = new StopThread();

  21.             Thread t1 = new Thread(st);
  22.             Thread t2 = new Thread(st);

  23.             t1.start();
  24.             t2.setDaemon( true);
  25.             t2.start();

  26.              int num = 1;
  27.              for(;;){
  28.                    if(++num == 50){
  29.                         t1.interrupt();
  30.                          break;
  31.                   }
  32.                   System. out.println("main..." + num);
  33.             }
  34.             System. out.println("over" );
  35.       }
  36. }
复制代码
   运行结果:
……


    join方法:


    示例:
  1. class Demo implements Runnable{
  2.        public void run(){
  3.              for(int x = 0; x < 50; x++){
  4.                   System. out.println(Thread.currentThread().getName() + "..." + x);
  5.             }
  6.       }
  7. }

  8. class JoinDemo{
  9.        public static void main(String[] args){
  10.             Demo d = new Demo();

  11.             Thread t1 = new Thread(d);
  12.             Thread t2 = new Thread(d);

  13.             t1.start();
  14.              try {
  15.                   t1.join(); //t1线程要申请加入进来,运行。然后,主线程等待t1执行完毕。
  16.                    //临时加入一个线程运算时可以使用join方法。
  17.             } catch (InterruptedException e) {
  18.                   e.printStackTrace();
  19.             }
  20.             t2.start();

  21.              for(int x = 0; x < 50; x++){
  22.                   System.out.println(Thread.currentThread().toString() + "..." + x);
  23.             }
  24.       }
  25. }
复制代码
    运行结果:
……


    setPriority方法:


    toString方法:


    yield方法:


    示例:
  1. class JoinDemo{
  2.        public static void main(String[] args){
  3.             Demo d = new Demo();

  4.             Thread t1 = new Thread(d);
  5.             Thread t2 = new Thread(d);

  6.             t1.start();
  7.             t2.start();
  8.             t2.setPriority(Thread. MAX_PRIORITY);

  9.              for(int x = 0; x < 50; x++){
  10.                   System. out.println(Thread.currentThread().toString() + "..." + x);
  11.             }
  12.       }
  13. }

  14. class Demo implements Runnable{
  15.        public void run(){
  16.              for(int x = 0; x < 50; x++){
  17.                   System. out.println(Thread.currentThread().getName() + "..." + x);
  18.                   Thread. yield();//释放执行权
  19.             }
  20.       }
  21. }
复制代码
    运行结果:
……

……


~END~



~爱上海,爱黑马~



40 个回复

倒序浏览
O(∩_∩)O谢谢!!!!!!!!!!!!!1111
回复 使用道具 举报
辛苦啦~~谢谢!
回复 使用道具 举报
阳哥笔记,必属精品,多看多学!
回复 使用道具 举报
学习中 加油
回复 使用道具 举报
看视频的时候就这部分最糊涂  有阳哥的这个笔记  大爱
回复 使用道具 举报
阳哥,辛苦!
回复 使用道具 举报
学习ING!
回复 使用道具 举报
jife94 高级黑马 2015-6-10 10:05:07
9#
谢谢了哈哈哈
回复 使用道具 举报
整理的很详细
回复 使用道具 举报
很好很强大,给力神贴!
回复 使用道具 举报
神贴来学习下
回复 使用道具 举报
看完视频,在复习一下笔记
回复 使用道具 举报
阳哥笔记,必属精品,多看多学!
回复 使用道具 举报
赞!!!
回复 使用道具 举报
辛苦了,学习中........加油!
回复 使用道具 举报
学习了~
回复 使用道具 举报
赞!!!
回复 使用道具 举报
顶一个!
回复 使用道具 举报
受教。。。。
回复 使用道具 举报
123下一页
您需要登录后才可以回帖 登录 | 加入黑马