回顾一个比较经典的线程间协作的问题:启动三个线程,每个线程相应的打印10遍A,10遍B,10遍C。要求三个线程交替执行,输处10遍ABC。
用Object类的notify(), wait()方法可实现上述要求。
Object.notify()可以唤醒一个线程,使之进入就绪状态,等待获取对象锁后运行。
Object.wait()方法可以使一个线程进入阻塞状态,然后释放对象锁,等待被notify()方法唤醒。
因为notify()和wait()方法都跟对象锁相关,所以必须在同步块里面被调用才有效。即都是如下形式:
synchronized(Obj){
try{
Obj.wait();
} catch (Exception e){
...
}
Obj.notify;
}
回看题目本身。要交替打印A,B,C,建立需要三个线程,三个对象。单个打印时,线程需要得到两个对象锁。
public class WatiNotifyTest {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
MyThread myThread1 = new MyThread("A", obj1, obj2);
MyThread myThread2 = new MyThread("B", obj3, obj1);
MyThread myThread3 = new MyThread("C", obj2, obj3);
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread {
private String name;
private Object a;
private Object b;
public MyThread(String name, Object a, Object b){
this.name = name;
this.a = a;
this.b = b;
}
@Override
public void run() {
int count = 10;
while(count > 0){
synchronized(a){
synchronized (b) {
System.out.println(name);//print "A" or "B" or "C".
count--;
/****************/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
/****************/
b.notify();//wake up the thread which is waiting for the lock of "b".
}
try {
a.wait();//let current thread release the lock on a.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程对象MyThread的run方法会执行10次循环,每次循环都打印出当前线程的name(A或B或C)。而两个synchronized块是实现交替打印的关键。
从main方法入手。建立三个对象,obj1,ojb2,obj3,共有三把对象锁。线程myThread1启动,执行myThread1的run方法。同理,myThread2,myThread3启动,执行各自的run方法。对于myThread1来说,a就是obj1,b就是obj2。同理,对于myThread2来说,a就是obj3,b就是obj1。如下是程序运行步骤:
1、myThread1进入while循环,进入synchronized块。myThread1获得了obj1和obj2两个对象锁。然后输出"A",sleep 10毫秒。调用obj2.notify(),唤醒在等待obj2的线程;调用obj1.wait(),释放obj1的锁。
2、在上述myThread1执行的过程中,myThread2进入while循环,然而它暂时只能进入第一个synchronized块,因为它需要obj1的锁才能进入第二个synchronized块,而此时obj1的锁在myThread1手中。当myThread1调用obj1.wait()之后,myThread2进入第二个synchronized块,获得obj3和obj1两个对象锁,然后输出"B",sleep 10毫秒,调用obj1.notify(),唤醒在等待obj1的线程;调用obj3.wait(),释放obj3的锁.
3、在myThread1执行完obj2.notify()并退出第二个synchronized块的时候,myThread3得到了obj2的对象锁,进入第一个synchronized块。当myThread2执行完obj3.wait()之后,获得obj3的对象锁,进入第二个synchronized块。然后输出"C",sleep 10毫秒。调用obj3.notify(),唤醒在等待obj3的线程;调用obj2.wait(),释放obj2的锁。
4、myThread1在第一次循环执行完后进入waiting状态,等待的是obj1的锁,以便进入第一个synchronized块。而当myThread2执行完毕的时候,调用了obj1.notify(),此时myThread1再次进入第一个synchronized块。等待myThread3执行完obj2.wait()释放obj2的锁之后,myThread1进入第二个synchronized块。然后重复1,2,3,4的步骤。
代码中/*****/之间的部分很重要,它保证了三个线程的执行顺序不受JVM随机调度的影响。
从上面例子可以看出,wait()和notify()方法,主要是控制对象的使用权的。notify()相当于告诉在waiting状态的线程说,某某资源我已经用完了,你可以来使用了。wait()方法相当于自动让出资源的使用权,进入阻塞状态(类似sleep,但是sleep会保留对象锁)。
注意wait()和notify()一般是一起使用的。在调用了Object.wait()的线程,只有在Object.notify()被调用之后,才能再次进入就绪状态,等待调度。