`
zhangwei217245
  • 浏览: 33565 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java 多线程同步问题的探究(四、协作,互斥下的协作——Java多线程协作(wait、notify、notifyAll))

阅读更多

Java监视器支持两种线程:互斥和协作。

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

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

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

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

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

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

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

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


转载注明出处:http://x- spirit.iteye.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.iteye.com/、http: //www.blogjava.net/zhangwei217245/
我们注意到,针对flag的同步块中,我们实际上已经更改了flag对对象的引用: flag="false";
转载注明出处:http://x- spirit.iteye.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   < 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.iteye.com/、http: //www.blogjava.net/zhangwei217245/
最后,我们构造汉堡店,让这个故事发生:
转载注明出处:http://x- spirit.iteye.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();
    }
}


运行这个程序吧,然后你会看到我们汉堡店的比赛进行的很好,只是不知道那些顾客是不是会被撑到。。。

 

读到这里,有的读者可能会想到前面介绍的重入锁ReentrantLock。有的读者会问:如

0
0
分享到:
评论
2 楼 chtx87_98 2012-08-15  
打算学学线程,感谢作者,挺适合我学的,不过程序中我发现一个bug
Cooker.makeHamberg()中
if  (pool.size()  <  pool.getMaxSize()) {
   //@1
      synchronized  (waiter) {

@1处pool锁释放了,接着锁的是waiter
在waiter还没把汉堡放进去的时候,容器中值是固定的,所有线程都能进入@1处
感觉应该把else中的pool同步块移动if else 外围。
刚学水平有限,不知道理解的对不对
1 楼 ricoyu 2011-02-21  
写得非常好! 受益匪浅!

相关推荐

    JAVA实现线程间同步与互斥生产者消费者问题

    在Java编程中,线程同步和互斥是多线程编程中的重要概念,它们用于解决多个线程同时访问共享资源时可能出现的问题。本项目通过一个生产者消费者问题的实例,展示了如何在Java中实现线程间的同步与互斥。 生产者消费...

    操作系统实验 多线程同步与互斥 java编写 有界面

    在“操作系统实验 多线程同步与互斥 java编写 有界面”的实验中,可能需要设计一个图形用户界面(GUI),通过按钮或事件触发线程的创建和同步操作,直观地展示线程间的交互和同步效果。例如,可以模拟银行账户转账,...

    Java多线程wait和notify

    总结来说,Java的 `wait()` 和 `notify()` 提供了一种在多线程环境中控制线程执行的机制。通过合理使用这些方法,我们可以实现线程间的协作,精确控制子线程的运行状态。然而,这种方式虽然灵活,但管理起来相对复杂...

    java多线程设计模式详解(PDF及源码)

    wait set——线程的休息室 wait方法——把线程放入wait set notify方法——从wait set拿出线程 notifyAll方法——从wait set拿出所有线程 wait、notify、notifyAll是Object类的方法 线程的状态移转 跟线程有关的其他...

    java多线程教程——一个课件彻底搞清多线程

    本教程将深入讲解Java线程的相关知识,包括进程与线程的基本概念、线程的创建和启动、多线程的互斥与同步、线程状态和线程控制以及死锁的概念。 首先,我们要理解进程与线程的区别。进程是操作系统资源分配的基本...

    java 多线程 同步详解

    然而,多线程环境下数据的安全性问题不容忽视,这就引出了Java中的同步机制。本文将深入探讨Java多线程同步的核心概念,特别是`synchronized`关键字的使用,以及锁定对象与锁定类的区别。 1. **线程安全问题** 在...

    java 多线程编程实战指南(核心 + 设计模式 完整版)

    - **线程间通信**:`wait()`, `notify()` 和 `notifyAll()` 方法用于线程间的协作,需要在同步块或方法中使用。 3. **线程池** - **Executor框架**:`ExecutorService`、`ThreadPoolExecutor`和`Executors`工厂类...

    java多线程详解(比较详细的阐述了多线程机制)

    另外,wait()、notify()和notifyAll()方法用于线程间的通信,但它们必须在同步块或方法中使用,以确保正确唤醒等待的线程。 Java还引入了Lock接口和相关的实现,如ReentrantLock,提供比synchronized更细粒度的控制...

    java多线程代码案例(创建线程,主线程,线程优先级,线程组,线程同步,线程间的通信)

    本文将深入探讨Java多线程中的关键知识点,包括创建线程、主线程、线程优先级、线程组、线程同步以及线程间的通信。 1. **创建线程** 在Java中,可以通过两种方式创建线程:继承`Thread`类或实现`Runnable`接口。...

    Java多线程编程核心技术_完整版_java_

    1. wait()、notify()和notifyAll():这三个方法用于线程间的通信,它们必须在同步环境中使用。 2. Condition接口:配合Lock使用,提供更灵活的线程间通信方式。 五、线程池 1. Executor框架:Java 5引入的...

    Java多线程实现异步调用实例

    `wait()`, `notify()`和`notifyAll()`方法用于线程间的协作;`Thread.join()`可以让主线程等待子线程完成后再继续执行。 “异步”标签则涉及到了程序设计中的非阻塞特性,有助于提升系统的并发能力和响应性。Java 8...

    Java多线程编程

    4. **Object方法**:`Object`类中的几个方法在多线程环境下特别重要,如`wait()`, `notify()`, 和 `notifyAll()`。这些方法用于对象监视器机制,它们必须在同步块或方法中使用,否则会导致`...

    Java多线程的总结

    Java提供了多种线程间通信的方法,如wait()、notify()和notifyAll(),它们必须在同步块或同步方法中使用,用于控制线程的执行顺序。此外,还可以使用BlockingQueue阻塞队列实现生产者消费者模式,实现线程间的协作。...

    java多线程笔记全手打

    2. volatile关键字:确保多线程环境下变量的可见性和有序性,但不具备互斥性。 3. Lock接口与ReentrantLock类:提供比synchronized更细粒度的锁控制,支持公平锁和非公平锁,以及可重入和可中断特性。 四、线程通信...

    Java多线程管理示例

    下面我们将深入探讨Java多线程的核心概念、同步机制、死锁问题以及wait/notify机制,以"生产者与消费者"的例子来具体阐述。 首先,了解Java中的线程。线程是操作系统分配CPU时间的基本单位,每个线程都有自己的程序...

    java多线程实现生产者和消费者

    3. **同步控制**:Java提供了多种同步工具,如`synchronized`关键字、`wait()`, `notify()`和`notifyAll()`方法,以及`java.util.concurrent`包下的`BlockingQueue`接口。`synchronized`用于互斥访问,`wait()`, `...

    Java多线程下载网络图片

    在Java编程中,多线程是一项关键技能,尤其在处理并发任务时,如我们的示例——"Java多线程下载网络图片"。这个场景展示了如何利用多线程技术提高程序性能,减少用户等待时间,同时优化系统资源的使用。下面我们将...

    java 多线程编程指南

    Java提供了wait()、notify()和notifyAll()方法,这些方法与synchronized配合使用,可以让线程在特定条件下等待或唤醒。另外,Java并发包(java.util.concurrent)提供了更高级的并发工具,如Semaphore(信号量)、...

    深入浅出 Java 多线程.pdf_java_

    Java多线程是Java编程中的核心概念,尤其在如今并发性能至关重要的软件开发中,它的重要性不言而喻。深入理解Java多线程能够帮助开发者有效地利用计算机资源,提高程序的执行效率,优化系统性能。 Java多线程的实现...

Global site tag (gtag.js) - Google Analytics