Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 264|回复: 0

[Java线程学习]Java多线程协作(wait、notify、notifyAll)

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-2 23:59:13 | 显示全部楼层 |阅读模式
    java监视器支持两种线程:互斥和协作。

        前面我们介绍了采用对象锁和重入锁来实现的互斥。这一篇中,我们来看一看线程的协作。

        举个例子:有一家汉堡店举办吃汉堡比赛,决赛时有3个顾客来吃,3个厨师来做,一个服务员负责协调汉堡的数量。为了避免浪费,制作好的汉堡被放进一个能装有10个汉堡的长条状容器中,按照先进先出的原则取汉堡。如果容器被装满,则厨师停止做汉堡,如果顾客发现容器内的汉堡吃完了,就可以拍响容器上的闹铃,提醒厨师再做几个汉堡出来。此时服务员过来安抚顾客,让他等待。而一旦厨师的汉堡做出来,就会让服务员通知顾客,汉堡做好了,让顾客继续过来取汉堡。

         这里,顾客其实就是我们所说的消费者,而厨师就是生产者。容器是决定厨师行为的监视器,而服务员则负责监视顾客的行为。

    JVM中,此种监视器被称为等待并唤醒监视器。

       
      
       
       
         
       

         
       
      
         在这种监视器中,一个已经持有该监视器的线程,可以通过调用监视对象的wait方法,暂停自身的执行,并释放监视器,自己进入一个等待区,直到监视器内的其他线程调用了监视对象的notify方法。 当一个线程调用唤醒命令以后,它会持续持有监视器,直到它主动释放监视器。而这之后,等待线程会苏醒,其中的一个会重新获得监视器,判断条件状态,以便决定是否继续进入等待状态或者执行监视区域,或者退出。

    请看下面的代码:



      
       
    1
    .
    public
      
    class
      NotifyTest {
       
    2
    .     
    private
       String flag
    =
      
    "
    true
    "
    ;
       
    3
    .  
       
    4
    .     
    class
      NotifyThread
    extends
      Thread{
       
    5
    .         
    public
      NotifyThread(String name) {
       
    6
    .            
    super
    (name);
       
    7
    .         }
       
    8
    .         
    public
      
    void
      run() {      
       
    9
    .            
    try
      {
       
    10
    .                 sleep(
    3000
    );
    //
    推迟3秒钟通知


       
    11
    .             }
    catch
      (InterruptedException e) {
       
    12
    .                 e.printStackTrace();
       
    13
    .             }
       
    14
    .              
       
    15
    .                 flag
    =
      
    "
    false
    "
    ;
       
    16
    .                 flag.notify();
       
    17
    .         }
       
    18
    .     };
       
    19
    .  
       
    20
    .     
    class
      WaitThread
    extends
      Thread {
       
    21
    .         
    public
      WaitThread(String name) {
       
    22
    .            
    super
    (name);
       
    23
    .         }
       
    24
    .  
       
    25
    .         
    public
      
    void
      run() {
       
    26
    .              
       
    27
    .                 
    while
      (flag
    !=
    "
    false
    "
    ) {
       
    28
    .                     System.out.println(getName()
    +
      
    "
      begin waiting!
    "
    );
       
    29
    .                     
    long
      waitTime
    =
      System.currentTimeMillis();
       
    30
    .                     
    try
      {
       
    31
    .                         flag.wait();
       
    32
    .                     }
    catch
      (InterruptedException e) {
       
    33
    .                         e.printStackTrace();
       
    34
    .                     }
       
    35
    .                     waitTime
    =
      System.currentTimeMillis()
    -
      waitTime;
       
    36
    .                     System.out.println(
    "
    wait time :
    "
    +
    waitTime);
       
    37
    .                 }
       
    38
    .                 System.out.println(getName()
    +
      
    "
      end waiting!
    "
    );
       
    39
    .              
       
    40
    .         }
       
    41
    .     }
       
    42
    .  
       
    43
    .     
    public
      
    static
      
    void
      main(String[] args)
    throws
      InterruptedException {
       
    44
    .         System.out.println(
    "
    Main Thread Run!
    "
    );
       
    45
    .         NotifyTest test
    =
      
    new
      NotifyTest();
       
    46
    .         NotifyThread notifyThread
    =
    test.
    new
      NotifyThread(
    "
    notify01
    "
    );
       
    47
    .         WaitThread waitThread01
    =
      test.
    new
      WaitThread(
    "
    waiter01
    "
    );
       
    48
    .         WaitThread waitThread02
    =
      test.
    new
      WaitThread(
    "
    waiter02
    "
    );
       
    49
    .         WaitThread waitThread03
    =
      test.
    new
      WaitThread(
    "
    waiter03
    "
    );
       
    50
    .         notifyThread.start();
       
    51
    .         waitThread01.start();
       
    52
    .         waitThread02.start();
       
    53
    .         waitThread03.start();
       
    54
    .     }
       
    55
    .  
       
    56
    . }  


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    这段代码启动了三个简单的wait线程,当他们处于等待状态以后,试图由一个notify线程来唤醒。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    运行这段程序,你会发现,满屏的java.lang.IllegalMonitorStateException,根本不是你想要的结果。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    请注意以下几个事实:
        1. 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
        2. 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。
        3. 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
        4. JVM基于多线程,默认情况下不能保证运行时线程的时序性。

    也就是说,当线程在调用某个对象的wait或者notify方法的时候,要先取得该对象的控制权,换句话说,就是进入这个对象的监视器。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    通过前面对同步的讨论,我们知道,要让一个线程进入某个对象的监视器,通常有三种方法:
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    1: 执行对象的某个同步实例方法
    2: 执行对象对应的同步静态方法
    3: 执行对该对象加同步锁的同步块

    显然,在上面的例程中,我们用第三种方法比较合适。

    于是我们将上面的wait和notify方法调用包在同步块中。



      
       
    1
    .            
    synchronized
      (flag) {
       
    2
    .                 flag
    =
      
    "
    false
    "
    ;
       
    3
    .                 flag.notify();
       
    4
    .             }  


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/


      
       
    1
    .            
    synchronized
      (flag) {
       
    2
    .                 
    while
      (flag
    !=
    "
    false
    "
    ) {
       
    3
    .                     System.out.println(getName()
    +
      
    "
      begin waiting!
    "
    );
       
    4
    .                     
    long
      waitTime
    =
      System.currentTimeMillis();
       
    5
    .                     
    try
      {
       
    6
    .                         flag.wait();
       
    7
    .                     }
    catch
      (InterruptedException e) {
       
    8
    .                         e.printStackTrace();
       
    9
    .                     }
       
    10
    .                     waitTime
    =
      System.currentTimeMillis()
    -
      waitTime;
       
    11
    .                     System.out.println(
    "
    wait time :
    "
    +
    waitTime);
       
    12
    .                 }
       
    13
    .                 System.out.println(getName()
    +
      
    "
      end waiting!
    "
    );
       
    14
    .             }  




    但是,运行这个程序,我们发现事与愿违。那个非法监视器异常又出现了。。。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    我们注意到,针对flag的同步块中,我们实际上已经更改了flag对对象的引用: flag="false";
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    显然,这样一来,同步块也无能为力了,因为我们根本不是针对唯一的一个对象在进行同步。

    我们不妨将flag封装到JavaBean或者数组中去,这样用JavaBean对象或者数组对象进行同步,就可以达到既能修改里面参数又不耽误同步的目的。



      
    1
    .
    private
        String flag[]
    =
      {
    "
    true
    "
    };





      
       
    1
    .         
    synchronized
      (flag) {
       
    2
    .             flag[
    0
    ]
    =
      
    "
    false
    "
    ;
       
    3
    .             flag.notify();
       
    4
    .         }  






      
       
    1
    .                  
    synchronized
      (flag) {
       
    2
    .                 flag[
    0
    ]
    =
      
    "
    false
    "
    ;
       
    3
    .                 flag.notify();
       
    4
    .             }
    synchronized
      (flag) {
       
    5
    .                 
    while
      (flag[
    0
    ]
    !=
    "
    false
    "
    ) {
       
    6
    .                     System.out.println(getName()
    +
      
    "
      begin waiting!
    "
    );
       
    7
    .                     
    long
      waitTime
    =
      System.currentTimeMillis();
       
    8
    .                     
    try
      {
       
    9
    .                         flag.wait();
       
    10
    .                          
       
    11
    .                     }
    catch
      (InterruptedException e) {
       
    12
    .                         e.printStackTrace();
       
    13
    .                     }  



    运行这个程序,看不到异常了。但是仔细观察结果,貌似只有一个线程被唤醒。利用jconsole等工具查看线程状态,发现的确还是有两个线程被阻塞的。这是为啥呢?

    程序中使用了flag.notify()方法。只能是随机的唤醒一个线程。我们可以改用flag.notifyAll()方法。这样,所有被阻塞的线程都会被唤醒了。

    最终代码请读者自己修改,这里不再赘述。

    好了,亲爱的读者们,让我们回到开篇提到的汉堡店大赛问题当中去,来看一看厨师、服务生和顾客是怎么协作进行这个比赛的。

    首先我们构造故事中的三个次要对象:汉堡包、存放汉堡包的容器、服务生



      
    public
      
    class
      Waiter {
    //
    服务生,这是个配角,不需要属性。


    }



      
         
    class
      Hamberg {
             
    //
    汉堡包


             
    private
      
    int
      id;
    //
    汉堡编号


             
    private
      String cookerid;
    //
    厨师编号


             
    public
      Hamberg(
    int
      id, String cookerid){
                
    this
    .id
    =
      id;
                
    this
    .cookerid
    =
      cookerid;
                 System.out.println(
    this
    .toString()
    +
    "
    was made!
    "
    );
             }

             @Override
             
    public
      String toString() {
                
    return
      
    "
    #
    "
    +
    id
    +
    "
      by
    "
    +
    cookerid;
             }
             
         }



      
         
    class
      HambergFifo {
             
    //
    汉堡包容器


             List
    <
    Hamberg
    >
      hambergs
    =
      
    new
      ArrayList
    <
    Hamberg
    >
    ();
    //
    借助ArrayList来存放汉堡包


             
    int
      maxSize
    =
      
    10
    ;
    //
    指定容器容量

             
    //
    放入汉堡


             
    public
      
    <
    T
    extends
      Hamberg
    >
      
    void
      push(T t) {
                 hambergs.add(t);
             }

             
    //
    取出汉堡


             
    public
      Hamberg pop() {
                 Hamberg h
    =
      hambergs.get(
    0
    );
                 hambergs.remove(
    0
    );
                
    return
      h;
             }

             
    //
    判断容器是否为空


             
    public
      
    boolean
      isEmpty() {
                
    return
      hambergs.isEmpty();
             }

             
    //
    判断容器内汉堡的个数


             
    public
      
    int
      size() {
                
    return
      hambergs.size();
             }

             
    //
    返回容器的最大容量


             
    public
      
    int
      getMaxSize() {
                
    return
      
    this
    .maxSize;
             }
         }

    接下来我们构造厨师对象:



      
         
    class
      Cooker
    implements
      Runnable {
             
    //
    厨师要面对容器


             HambergFifo pool;
             
    //
    还要面对服务生


             Waiter waiter;

             
    public
      Cooker(Waiter waiter, HambergFifo hambergStack) {
                
    this
    .pool
    =
      hambergStack;
                
    this
    .waiter
    =
      waiter;
             }
             
    //
    制造汉堡


             
    public
      
    void
      makeHamberg() {
                
    //
    制造的个数


                
    int
      madeCount
    =
      
    0
    ;
                
    //
    因为容器满,被迫等待的次数


                
    int
      fullFiredCount
    =
      
    0
    ;
                
    try
      {
                     
                     
    while
      (
    true
    ) {
                         
    //
    制作汉堡前的准备工作


                         Thread.sleep(
    1000
    );


                         
    if
      (pool.size()
    <
      pool.getMaxSize()) {
                            
    synchronized
      (waiter) {
                                 
    //
    容器未满,制作汉堡,并放入容器。


                                 pool.push(
    new
      Hamberg(
    ++
    madeCount,Thread.currentThread().getName()));

                                 
    //
    说出容器内汉堡数量


                                 System.out.println(Thread.currentThread().getName()
    +
      
    "
    : There are
    "
      

                                               +
      pool.size()
    +
      
    "
      Hambergs in all
    "
    );


                                 
    //
    让服务生通知顾客,有汉堡可以吃了


                                 waiter.notifyAll();


                                 System.out.println(
    "
    ### Cooker: waiter.notifyAll() :"+
                                           " Hi! Customers, we got some new Hambergs!
    "
    );
                             }
                         }
    else
      {
                            
    synchronized
      (pool) {
                                 
    if
      (fullFiredCount
    ++
      
    <
      
    10
    ) {
                                     
    //
    发现容器满了,停止做汉堡的尝试。


                                     System.out.println(Thread.currentThread().getName()
    +
      

                                             "
    : Hamberg Pool is Full, Stop making hamberg
    "
    );


                                     System.out.println(
    "
    ### Cooker: pool.wait()
    "
    );
                                     
    //
    汉堡容器的状况使厨师等待


                                     pool.wait();


                                 }
    else
      {
                                     
    return
    ;
                                 }
                             }

                         }
                         
                         
    //
    做完汉堡要进行收尾工作,为下一次的制作做准备。


                         Thread.sleep(
    1000
    );



                     }
                 }
    catch
      (Exception e) {
                     madeCount
    --
    ;
                     e.printStackTrace();
                 }
             }

             
    public
      
    void
      run() {

                 makeHamberg();

             }
         }

    接下来,我们构造顾客对象:



      
         
    class
      Customer
    implements
      Runnable {
             
    //
    顾客要面对服务生


             Waiter waiter;
             
    //
    也要面对汉堡包容器


             HambergFifo pool;
             
    //
    想要记下自己吃了多少汉堡


             
    int
      ateCount
    =
      
    0
    ;
             
    //
    吃每个汉堡的时间不尽相同


             
    long
      sleeptime;
             
    //
    用于产生随机数


             Random r
    =
      
    new
      Random();

             
    public
      Customer(Waiter waiter, HambergFifo pool) {
                
    this
    .waiter
    =
      waiter;
                
    this
    .pool
    =
      pool;
             }

             
    public
      
    void
      run() {

                
    while
      (
    true
    ) {

                     
    try
      {
                         
    //
    取汉堡


                         getHamberg();
                         
    //
    吃汉堡


                         eatHamberg();
                     }
    catch
      (Exception e) {
                         
    synchronized
      (waiter) {
                             System.out.println(e.getMessage());
                            
    //
    若取不到汉堡,要和服务生打交道


                            
    try
      {
                                 System.out.println(
    "
    ### Customer: waiter.wait():"+
                                             " Sorry, Sir, there is no hambergs left, please wait!
    "
    );
                                 System.out.println(Thread.currentThread().getName()

                                             +
      
    "
    : OK, Waiting for new hambergs
    "
    );
                                 
    //
    服务生安抚顾客,让他等待。


                                 waiter.wait();
                                 
    continue
    ;
                             }
    catch
      (InterruptedException ex) {
                                 ex.printStackTrace();
                             }
                         }
                     }
                 }
             }

             
    private
      
    void
      eatHamberg() {
                
    try
      {
                     
    //
    吃每个汉堡的时间不等


                     sleeptime
    =
      Math.abs(r.nextInt(
    3000
    ))
    *
      
    5
    ;
                     System.out.println(Thread.currentThread().getName()
                            
    +
      
    "
    : I"m eating the hamberg for
    "
      
    +
      sleeptime
    +
      
    "
      milliseconds
    "
    );
                     
                     Thread.sleep(sleeptime);
                 }
    catch
      (Exception e) {
                     e.printStackTrace();
                 }
             }

             
    private
      
    void
      getHamberg()
    throws
      Exception {
                 Hamberg hamberg
    =
      
    null
    ;

                
    synchronized
      (pool) {
                     
    try
      {
                         
    //
    在容器内取汉堡


                         hamberg
    =
      pool.pop();

                         ateCount
    ++
    ;
                         System.out.println(Thread.currentThread().getName()

                                    +
      
    "
    : I Got
    "
      
    +
      ateCount
    +
      
    "
      Hamberg
    "
      
    +
      hamberg);
                         System.out.println(Thread.currentThread().getName()

                                     +
      
    "
    : There are still
    "
      
    +
      pool.size()
    +
      
    "
      hambergs left
    "
    );


                     }
    catch
      (Exception e) {
                         pool.notifyAll();
                         System.out.println(
    "
    ### Customer: pool.notifyAll()
    "
    );
                         
    throw
      
    new
      Exception(Thread.currentThread().getName()
    +
      

                                 "
    : OH MY GOD!!!! No hambergs left, Waiter![Ring the bell besides the hamberg pool]
    "
    );

                     }
                 }
             }
         }

    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    最后,我们构造汉堡店,让这个故事发生:
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/


      
    public
      
    class
      HambergShop {

         Waiter waiter
    =
      
    new
      Waiter();
         HambergFifo hambergPool
    =
      
    new
      HambergFifo();
         Customer c1
    =
      
    new
      Customer(waiter, hambergPool);
         Customer c2
    =
      
    new
      Customer(waiter, hambergPool);
         Customer c3
    =
      
    new
      Customer(waiter, hambergPool);
         Cooker cooker
    =
      
    new
      Cooker(waiter, hambergPool);

         
    public
      
    static
      
    void
      main(String[] args) {
             HambergShop hambergShop
    =
      
    new
      HambergShop();
             Thread t1
    =
      
    new
      Thread(hambergShop.c1,
    "
    Customer 1
    "
    );
             Thread t2
    =
      
    new
      Thread(hambergShop.c2,
    "
    Customer 2
    "
    );
             Thread t3
    =
      
    new
      Thread(hambergShop.c3,
    "
    Customer 3
    "
    );
             Thread t4
    =
      
    new
      Thread(hambergShop.cooker,
    "
    Cooker 1
    "
    );
             Thread t5
    =
      
    new
      Thread(hambergShop.cooker,
    "
    Cooker 2
    "
    );
             Thread t6
    =
      
    new
      Thread(hambergShop.cooker,
    "
    Cooker 3
    "
    );
             t4.start();
             t5.start();
             t6.start();
             
    try
      {
                 Thread.sleep(
    10000
    );
             }
    catch
      (Exception e) {
             }

             t1.start();
             t2.start();
             t3.start();
         }
    }

    运行这个程序吧,然后你会看到我们汉堡店的比赛进行的很好,只是不   知道那些顾客是不是会被撑到。。。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    读到这里,有的读者可能会想到前面介绍的重入锁ReentrantLock。
    有的读者会问:如果我用ReentrantLock来代替上面这些例程当中的 synchronized块,是不是也可以呢?感兴趣的读者不妨一试。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    但是在这里,我想提前给出结论,就是,
    如果用ReentrantLock的lock()和unlock()方法代替上面的synchronized块,那么上面这些程序还是要抛出java.lang.IllegalMonitorStateException异常的,不仅如此,你甚至还会看到线程死锁。原因就是当某个线程调用第三方对象的wait或者notify方法的时候,并没有进入第三方对象的监视器,于是抛出了异常信息。但此时,程序流程如果没有用finally来处理unlock方法,那么你的线程已经被lock方法上锁,并且无法解锁。程序在java.util.concurrent框架的语义级别死锁了,你用JConsole这种工具来检测JVM死锁,还检测不出来。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    正确的做法就是,只使用ReentrantLock,而不使用wait或者notify方法。因为ReentrantLock已经对这种互斥和协作进行了概括。所以,根据你程序的需要,请单独采用重入锁或者synchronized一种同步机制,最好不要混用。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    好了,我们现在明白:转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
    1. 线程的等待或者唤醒,并不是让线程调用自己的wait或者notify方法,而是通过调用线程共享对象的wait或者notify方法来实现。
    2. 线程要调用某个对象的wait或者notify方法,必须先取得该对象的监视器。
    3. 线程的协作必须以线程的互斥为前提,这种协作实际上是一种互斥下的协作。
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangw

      
      
       
       

         
       

         
       
      
    复制代码
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 16:28 , Processed in 0.307392 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表