Java监视器支持两种线程:互斥和协作。 |
在这种监视器中,一个已经持有该监视器的线程,可以通过调用监视对象的wait方法,暂停自身的执行,并释放监视器,自己进入一个等待区,直到监视器内的其他线程调用了监视对象的notify方法。 当一个线程调用唤醒命令以后,它会持续持有监视器,直到它主动释放监视器。而这之后,等待线程会苏醒,其中的一个会重新获得监视器,判断条件状态,以便决定是否继续进入等待状态或者执行监视区域,或者退出。
请看下面的代码:
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方法调用包在同步块中。
2 . flag = " false " ;
3 . flag.notify();
4 . }
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
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对象或者数组对象进行同步,就可以达到既能修改里面参数又不耽误同步的目的。
2 . flag[ 0 ] = " false " ;
3 . flag.notify();
4 . }
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()方法。这样,所有被阻塞的线程都会被唤醒了。
最终代码请读者自己修改,这里不再赘述。
好了,亲爱的读者们,让我们回到开篇提到的汉堡店大赛问题当中去,来看一看厨师、服务生和顾客是怎么协作进行这个比赛的。
首先我们构造故事中的三个次要对象:汉堡包、存放汉堡包的容器、服务生
}
// 汉堡包
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;
}
}
// 汉堡包容器
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;
}
}
接下来我们构造厨师对象:
// 厨师要面对容器
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();
}
}
接下来,我们构造顾客对象:
// 顾客要面对服务生
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/
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