JAVA 多线程(6):等待、通知 (1)
一、wait、notify、notifAll
所有Object都有这三个方法。
wait :当前线程等待锁(放弃当前线程持有的锁)
notify:随机通知等待此锁的线程准备获取锁
notifyALL:释放锁并通知所有等待此锁的线程
整个等待与通知的过程,类似餐馆上菜,服务员等待菜,厨师(线程)做好菜(锁)通知服务员(线程)。
在等待通知的过程中,必须要有锁的存在,也就是说必须要持有锁,才能进行等待与通知,如果没有则会出现异常。
如下:
public static void test(TestWait lock){ synchronized (lock){ System.out.println(Thread.currentThread().getName()+":"+"等菜"); try { lock.wait(); System.out.println(Thread.currentThread().getName()+":"+"上完了,再点菜"); lock.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void test2(TestWait lock){ synchronized (lock){ System.out.println(Thread.currentThread().getName()+":"+"做好了"); try { lock.notify(); System.out.println(Thread.currentThread().getName()+":"+"等客人"); lock.wait(); System.out.println("厨师发现没有原料了,下班"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { TestWait testWait = new TestWait(); Thread t = new Thread(new Runnable() { @Override public void run() { test(testWait); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { test2(testWait); } }); t.start(); Thread.sleep(1000); t2.start(); }
输出:
由结果看出,线程1与线程2通过wait、notify 实现了通信(前提是锁一致)
notify针对的是随机通知一个线程,如果争夺锁的线程太多,而又一个线程的优先级打个比方较低,有可能会一致都争夺不到锁。
上面的这种可能性较低,但是如果调用notify次数不够怎么办,假设有A,B 2个线程调用wait方法放弃锁,然后等待,线程C 拿到锁后调用notify,那么线程A与线程B只有一个能获取到锁,另一个只能继续等待通知,等待一个永远获取不到的锁。
这时候就必须通过notifyAll 来通知A和B,这样把wait状态的他们解放出来,加入到争夺锁的队列中去。
还有一种情况就是通知过早,如果线程Await,线程Bnotify,锁为C,如果线程B先通知了,那么A 就永远在等待C锁了。
如果遇到感觉可能会出现这种情况,最好加上一个标识用来判断是否有必要等待。
private static boolean isWait = true; public static void test(TestWait lock){ synchronized (lock){ try { // 还要不要等菜 if(isWait){ lock.wait(); System.out.println(Thread.currentThread().getName()+":"+"等菜"); } System.out.println(Thread.currentThread().getName()+":"+"上完了"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void test2(TestWait lock){ synchronized (lock){ System.out.println(Thread.currentThread().getName()+":"+"做好了"); lock.notify(); isWait = false; } } public static void main(String[] args) throws InterruptedException { TestWait testWait = new TestWait(); Thread t = new Thread(new Runnable() { @Override public void run() { test(testWait); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { test2(testWait); } }); Thread A = new Thread(t); t2.start(); Thread.sleep(2000); A.start(); }
输出:
结果看出,服务员不要再等待了,菜已经做好,直接上就行了。
wait等待造成状态改变
多个现在执行wait等待后,在某些条件改变后,有可能会出现意想不到的异常,如:
private static boolean isWait = true; private static List list = new ArrayList(); public static void test(TestWait lock){ synchronized (lock){ try { // 还要不要等菜 if(isWait){
if(list.size() == 0){ lock.wait(); System.out.println(Thread.currentThread().getName()+":"+"等菜");
} } list.remove(0); System.out.println(Thread.currentThread().getName()+":"+"上完了"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void test2(TestWait lock){ synchronized (lock){ System.out.println(Thread.currentThread().getName()+":"+"做好了"); list.add("菜"); lock.notifyAll(); isWait = false; } } public static void main(String[] args) throws InterruptedException { TestWait testWait = new TestWait(); Thread t = new Thread(new Runnable() { @Override public void run() { test(testWait); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { test2(testWait); } }); Thread A = new Thread(t); A.start(); Thread B = new Thread(t); B.start(); Thread.sleep(2000); t2.start(); }
输出:
结果是越界了,因为厨师只做了一道菜。
解决方法:轮询
while (list.size() == 0){ System.out.println(Thread.currentThread().getName()+":"+"等菜"); lock.wait(); }
输出:
第二个服务员继续等菜。(此事程序未结束)
生存者/消费者
private String str = ""; class P { public void setV(Object o){ synchronized (o){ if(!str.equals("")){ try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } str = Math.random() + ""; System.out.println("Set的值:"+str); o.notifyAll(); } } } class C { public void getV(Object o){ synchronized (o){ if(str.equals("")){ try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Get的值:"+str); str = ""; o.notifyAll(); } } } public static void main(String[] args){ Object lock = new Object(); TestPC pc = new TestPC(); P p = pc.new P(); C c = pc.new C(); Thread t = new Thread(new Runnable() { @Override public void run() { while (true){ c.getV(lock); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (true){ p.setV(lock); } } }); t.start(); t2.start(); }
输出:
上面是1对1,所以使用notify也是可以的,但是如果为多生产和多消费的话,就必须使用notifyAll 了。
还有如果对应的是1生产多消费,那么除了使用notifyAll 还要使用轮询机制,也就是while,类似上面的2个服务员一个厨师的例子。