Java并发编程中的等待/通知范式
“等待-通知”是挺有用的一个编程范式、设计模式或者说套路。白话讲就是一个线程执行需要等待一个事件发生或者说一个条件具备,然后另一个线程去触发这个事件或者更新这个条件,那么前者就可以触发执行了。这种设计可以解藕两个线程的业务逻辑,类似于生产者和消费者。
实现这个模式,主要有4种方法:线程自旋不断的去轮询条件是否具备、Object监视器方法wait()/notify() 、Condition接口、LockSupport的park/unpark方法。
1、比较容易想到的“经典的方法”
一个线程不断的自旋,去查询自己能够继续执行的条件,一旦发现条件具备(另一个线程修改了条件),则结束自旋,去执行业务逻辑。
package com.wangan.concurrent; import java.util.concurrent.TimeUnit; public class WaitAndNotifyPattern { public static volatile boolean flag = false; public static void main(String[] args) { new Thread(new Wait()).start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Notify()).start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } class Wait implements Runnable{ @Override public void run() { while(!WaitAndNotifyPattern.flag) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("条件还未满足,睡1秒"); } System.out.println("wait条件满足了,执行业务逻辑"); } } class Notify implements Runnable{ @Override public void run() { WaitAndNotifyPattern.flag = true; System.out.println("notify修改了条件为true"); } }
输出:
条件还未满足,睡1秒
条件还未满足,睡1秒
条件还未满足,睡1秒
条件还未满足,睡1秒
notify修改了条件为true
条件还未满足,睡1秒 这里体现了发现条件不及时
wait条件满足了,执行业务逻辑
这个方法最大的缺陷在于需要线程sleep,休眠多少时间是一对儿矛盾,少了的话cpu受不了,多了的话条件变了这边发现不及时。
所以就有了下面两个新方法。
2、使用Object监视器方法配合synchronized实现等待通知机制
package com.wangan.basic; import java.util.concurrent.TimeUnit; public class WaitAndNotify { public static Object lock = new Object(); public static boolean flag = false; public static void main(String[] args) { new Thread(new Wait()).start(); sleepSecond(5); new Thread(new Notify()).start(); } public static void sleepSecond(int sec) { try { TimeUnit.SECONDS.sleep(sec); } catch (InterruptedException e) { e.printStackTrace(); } } } class Wait implements Runnable{ @Override public void run() { synchronized(WaitAndNotify.lock) { while(!WaitAndNotify.flag) { try { System.out.println("Wait还未具备条件,等待"); WaitAndNotify.lock.wait(); //等待并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Wait获得了条件,执行业务逻辑"); } } } class Notify implements Runnable{ @Override public void run() { synchronized(WaitAndNotify.lock) { WaitAndNotify.flag = true; WaitAndNotify.lock.notifyAll(); System.out.println("Notify进行通知了、也修改了条件,但是Wait不会马上从wait()返回继续执行,需要等待Notify释放锁,并且还得竞争到这个锁"); WaitAndNotify.sleepSecond(5); System.out.println("Notify在通知后延迟了5秒释放了锁"); } synchronized(WaitAndNotify.lock) { System.out.println("Notify的恶作剧,证明Wait接收到通知之后仍要竞争Lock才能从wait()方法返回继续执行"); } } }
输出:
Wait还未具备条件,等待
Notify进行通知了、也修改了条件,但是Wait不会马上从wait()返回继续执行,需要等待Notify释放锁,并且还得竞争到这个锁
Notify在通知后延迟了5秒释放了锁
Notify的恶作剧,证明Wait接收到通知之后仍要竞争Lock才能从wait()方法返回继续执行 这句不在最后输出出来说明本次执行Wait在得到通知之后竞争锁又被Notify抢走了
Wait获得了条件,执行业务逻辑
3、使用Condition配合Lock实现等待通知机制
package com.wangan.basic; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionWaitAndNotify { public static Lock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); //Condition是依附于1个锁的 public static boolean flag = false; public static void main(String[] args) { new Thread(new Consumer()).start(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) {} new Thread(new Producer()).start(); } } class Consumer implements Runnable{ @Override public void run() { ConditionWaitAndNotify.lock.lock();//先拿到锁 try { while(!ConditionWaitAndNotify.flag) { System.out.println("Consumer条件尚未具备,等待"); ConditionWaitAndNotify.condition.await();//在锁的Condition上等待 } System.out.println("Consumer收到通知了、条件已具备,继续执行业务逻辑"); }catch (InterruptedException e) { e.printStackTrace(); }finally { ConditionWaitAndNotify.lock.unlock(); } } } class Producer implements Runnable{ @Override public void run() { ConditionWaitAndNotify.lock.lock(); try { ConditionWaitAndNotify.flag = true; ConditionWaitAndNotify.condition.signalAll(); System.out.println("Producer向Consumer通知条件已具备"); }finally { ConditionWaitAndNotify.lock.unlock(); } ConditionWaitAndNotify.lock.lock(); //跟Consumer抢锁 System.out.println("Producer的恶作剧,发完通知用完了还跟Consumer抢一下锁"); ConditionWaitAndNotify.lock.unlock(); } }
输出:
Consumer条件尚未具备,等待
Producer向Consumer通知条件已具备
Producer的恶作剧,发完通知用完了还跟Consumer抢一下锁
Consumer收到通知了、条件已具备,继续执行业务逻辑
4、使用LockSupport实现
大致思路:一个线程consumer,它等待条件变量,如果不满足则LockSupport.park,另一个线程如果完成了相应逻辑则修改volatile条件变量为满足,并唤醒前一个线程继续执行。
这样consumer线程继续执行就会发现条件变量已经满足。
public class WaitAndNotifyLockSupport { public static volatile boolean flag = false; public static void main(String[] args) { Thread consumer = new Thread(new Consumer()); consumer.setName("Consumer"); consumer.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Producer(consumer)).start(); } } class Consumer implements Runnable{ private Logger logger = LoggerFactory.getLogger(Consumer.class); @Override public void run() { while(!WaitAndNotifyLockSupport.flag) { logger.info("flag为" + WaitAndNotifyLockSupport.flag); logger.info("Consumer park阻塞"); LockSupport.park(); //consumer线程将会阻塞在此,等待另一个线程唤醒之后继续往下执行 logger.info("Consumer 被唤醒继续执行"); logger.info("flag为" + WaitAndNotifyLockSupport.flag); } } } class Producer implements Runnable{ private Logger logger = LoggerFactory.getLogger(Producer.class); private Thread needToUnpark; public Producer(Thread t) { needToUnpark = t; } @Override public void run() { logger.info("Producer设置flag为true"); WaitAndNotifyLockSupport.flag = true; logger.info("Producer唤醒" + needToUnpark.getName()); LockSupport.unpark(needToUnpark); //唤醒consumer线程 } }
使用LockSupport.lock()阻塞的consumer线程,我们可以通过jstack看下它的状态
"Consumer" #11 prio=5 os_prio=0 tid=0x0000000019e7a000 nid=0x5b84 waiting on condition [0x000000001ae0f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304) at com.wangan.lock.support.Consumer.run(WaitAndNotifyLockSupport.java:35) at java.lang.Thread.run(Thread.java:748)
处于WAITING(parking)状态。