wait/notify
等待(wait)
: 一个线程因执行目标动作所需的保护条件未满足而被暂停的过程。通知(Notify)
: 一个线程更新了系统的状态,使得其他线程所需的保护条件得以满足的时候唤醒那些被暂停的线程的过程。- java中使用Object类的wait()和wait(long)来实现
等待(wait)
,使用Object类的notify()和notifyAll()可用于实现通知(Notify)
。
wait
-
wait()
方法是Object的方法,该方法用来将当前线程置于"预执行队列"中,并且在wait()所在的代码处停止执行,直到接到其他通知(Notify)
或者中断为止。 -
在调用
wait()
方法之前,线程必须获得该对象的对象级别锁。即只能在同步方法或者同步代码块中调用wait()方法。在执行wait方法后,该线程释放锁,在从wait()返回前,线程与其它线程竞争重新获得锁。 -
如果调用
wait()
方法时没有持有适当的锁,则抛出运行时异常IllegalMonitorStateException
public class IllegalMontiorStateExceptionTest { public static void main(String[] args) { try { String str = new String(""); str.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class Test { public static void main(String[] args) { try { String lock = new String(); System.out.println("syn上面....."); synchronized (lock) { System.out.println("syn第一行....."); lock.wait(); System.out.println("syn中wait的下面"); } System.out.println("syn后面....."); } catch (InterruptedException e) { e.printStackTrace(); } } } 输出: syn上面..... syn第一行.....
-
设obj为java中任意一个类的实例,因执行obj.wait()而被暂停的线程就称为对象obj上的
等待线程
。由于同一个对象的同一个方法可以被多个线程执行,因此一个对象可能存在多个等待线程。obj上的等待线程可以通过其他线程执行obj.notify()来唤醒。obj.wait()会以原子操作的方式使其执行线程暂停并使该线程释放其持有的obj对应的内部锁。 -
一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。这将允许其他线程也可以调用wait()或者notify()。
notify
- notify也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify没有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象锁的其他线程,如果有多个线程等待,则由线程规划器挑选出一个呈wait状态的线程,对其发出通知notify.并使它等待获取该对象的对象锁。
- 在执行notify方法后,当前线程不会马上释放该对象锁。呈wait状态的线程也不能马上获取该对象锁,要等到执行notfiy方法的线程将程序执行完,也就是退出同步(synchronized)代码块后,当前线程才会释放锁。而呈wait状态所在的线程才可以获取该对象锁。
- 当一个获得了该对象锁的wait线程运行完毕之后,他会释放掉该对象锁,此时如果该对象没有再次使用notify,则即便是该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notfiy或者notifyAll
- 如果发出的notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。
- notify方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态。notifyAll方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态。优先级最高的线程最先执行,但也可能是随机执行(这要取决于JVM的具体实现)
虚假唤醒
-
虚假唤醒(spurious wakeups):虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。
-
虚假唤醒发生的条件
- 当一个数据存在三个及以上的线程竞争访问时(必要条件)
- 至少有一个线程没有对数据进行加锁访问(充分条件,使得虚假唤醒发生可能)
-
解决虚假唤醒方法
- 方法一:所有的线程访问数据时都加锁
- 方法二:在循环中等待,即必须使用while来等待条件,而不能使用if。这样的while循环是一个自旋锁。目前的JVM实现自旋会消耗CPU,如果长时间不调用notify()或者notifyAll()方法,wait()方法会一直自旋,CPU会消耗太大
-
虚假唤醒举例
- 一个生产者producer,两个消费者(consumerA和consumerB),一个队列Queue
- consumerA和consumerB先启动,此时因为队列是空,所以consumerA和consumerB都进入等待队列(Wait Set)
- producer产生一条消息到队列中,此时queue大小为1,然后调用notifyAll(),此时consumerA和consumerB都被唤醒
- 假设consumerA先竞争到锁,消费一个消息,此时queue大小为0,完成后释放锁
- consumerB竞争到锁,消费一条消息,此时queue大小为-1,抛出索引越界异常
-
虚假唤醒代码示例
public class SpuriousWakeups { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); Queue<Integer> queue = new LinkedList<>(); Producer producer = new Producer(queue); Consumer consumerA = new Consumer(queue, "consumerA", countDownLatch); Consumer consumerB = new Consumer(queue, "consumerB", countDownLatch); ExecutorService executor = Executors.newFixedThreadPool(6); //先启动两个consumer executor.submit(consumerA); executor.submit(consumerB); countDownLatch.await(); //再启动produer executor.submit(producer); Thread.sleep(10000); executor.shutdown(); } } class Producer implements Runnable { private final Queue<Integer> queue; public Producer(Queue<Integer> queue) { this.queue = queue; } private boolean notEmpty() { return queue.size() >= 1; } @Override public void run() { while (true) { try { synchronized (queue) { if (notEmpty()) { System.out.println("producer wait..."); queue.wait(); System.out.println("producer wake up..."); } int time = new Random().nextInt(100); Thread.sleep(time); System.out.println("producer add:" + time); queue.add(time); queue.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } } } class Consumer implements Runnable { private final Queue<Integer> queue; private final String name; private CountDownLatch countDownLatch; public Consumer(Queue<Integer> queue, final String name, CountDownLatch countDownLatch) { this.queue = queue; this.name = name; this.countDownLatch = countDownLatch; } private boolean empty() { return queue.size() <= 0; } @Override public void run() { countDownLatch.countDown(); Thread.currentThread().setName(name); while (true) { try { synchronized (queue) { if (empty()) { System.out.println(Thread.currentThread().getName() + " wait..."); queue.wait(); System.out.println(Thread.currentThread().getName() + " wake up..."); } Integer time = new Random().nextInt(100); Thread.sleep(time); System.out.println(Thread.currentThread().getName() + " remove " + queue.remove()); // 也有可能唤醒另一个consumer中的queue.wait queue.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } } } 输出结果 consumerA wait... consumerB wait... producer add:13 producer wait... consumerB wake up... consumerB remove 13 consumerB wait... consumerA wake up... producer wake up... java.util.NoSuchElementException at java.util.LinkedList.removeFirst(LinkedList.java:270) at java.util.LinkedList.remove(LinkedList.java:685) at cn.jannal.other.Consumer.run(SpuriousWakeups.java:105) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) ...省略... 输出结果分析: 1. consumerA和consumerB先启动进入等待队列 2. produer添加完元素之后,唤醒consumer(此处是唤醒ConsumerB),produer进入等待队列 3. ConsumerB执行之后唤醒producer和ConsumerA,导致ConsumerA执行remve时获取不到元素抛出异常。 将上面的if语句修改为while语句,再次查看输出结果,不会有任何异常,因为consumerA无法执行到remove语句,并不满足条件 while (notEmpty()) { System.out.println("producer wait..."); queue.wait(); System.out.println("producer wake up..."); } while (empty()) { System.out.println(Thread.currentThread().getName() + " wait..."); queue.wait(); System.out.println(Thread.currentThread().getName() + " wake up..."); } consumerA wait... consumerB wait... producer add:76 producer wait... consumerB wake up... consumerB remove 76 consumerB wait... consumerA wake up... consumerA wait... ...省略...
Condition(等待通知)
-
使用
Condition
实现等待/通知:错误用法与解决,Condition
是JDK5中的技术,有更好的灵活性,可以实现多路通知功能,也就是一个Lock对象里面可以创建多个Condition(对象监视器)对象实例。线程对象可以注册在指定的Condition中。从而可以有选择的进行线程通知。 -
synchronized
相当于整合Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象身上。线程开始notifyAll(),需要通知所有的waiting线程,没有选择权,随机性比较大。-
Object中的wait()方法相当于Condition类中的await();
-
Object中的notify()方法相当于Condition类中的signal()方法
-
Object中notifyAll()方法相当于Condition类的signalAll()方法
-
-
Condition案例(单一)
public class ConditionService { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void await(){ lock.lock(); System.out.println("已经开始锁了,并开始进入waiting状态"); try { //使当前执行任务的线程进入等待状态(WAITING) condition.await(); System.out.println("已经结束waiting状态"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); System.out.println("锁释放了"); } } public void signal(){ lock.lock(); System.out.println("已经获得锁,并开始进入通知状态(发信号状态)"); try { condition.signal(); } finally{ lock.unlock(); } } } public class ThreadA extends Thread { private ConditionService conditionService; public ThreadA(ConditionService conditionService) { this.conditionService = conditionService; } @Override public void run() { conditionService.await(); } } public class Main { public static void main(String[] args) throws InterruptedException { ConditionService conditionService = new ConditionService(); ThreadA a = new ThreadA(conditionService); a.start(); TimeUnit.SECONDS.sleep(10); conditionService.signal(); } }
-
多个Condition
public class ConditionService { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void awaitA(){ lock.lock(); System.out.println("A已经开始锁了,并开始进入waiting状态"); try { //使当前执行任务的线程进入等待状态(WAITING) condition.await(); System.out.println("A已经结束waiting状态"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); System.out.println("A锁释放了"); } } public void awaitB(){ lock.lock(); System.out.println("B已经开始锁了,并开始进入waiting状态"); try { //使当前执行任务的线程进入等待状态(WAITING) condition.await(); System.out.println("B已经结束waiting状态"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); System.out.println("B锁释放了"); } } public void signalAll(){ lock.lock(); System.out.println("已经获得锁,并开始进入通知状态(发信号状态)"); try { condition.signalAll(); } finally{ lock.unlock(); } } } public class ThreadA extends Thread { private ConditionService conditionService; public ThreadA(ConditionService conditionService) { this.conditionService = conditionService; } @Override public void run() { conditionService.awaitA(); } } public class ThreadB extends Thread { private ConditionService conditionService; public ThreadB(ConditionService conditionService) { this.conditionService = conditionService; } @Override public void run() { conditionService.awaitB(); } } public class Main { public static void main(String[] args) throws InterruptedException { ConditionService conditionService = new ConditionService(); ThreadA a = new ThreadA(conditionService); a.start(); ThreadB b = new ThreadB(conditionService); b.start(); TimeUnit.SECONDS.sleep(10); conditionService.signalAll(); } } A已经开始锁了,并开始进入waiting状态 B已经开始锁了,并开始进入waiting状态 已经获得锁,并开始进入通知状态(发信号状态) A已经结束waiting状态 A锁释放了 B已经结束waiting状态 B锁释放了
-
生产者消费者一一交替打印
public class ConditionService { private ReentrantLock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private boolean hasValue = false; public void set(){ setAndGet(true,"黑色"); } public void get(){ setAndGet(false,"白色"); } public void setAndGet(boolean flag,String color){ try { lock.lock(); while(hasValue==flag){ condition.await(); } System.out.println(color); hasValue = flag; condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } } } public class ThreadA extends Thread { private ConditionService conditionService; public ThreadA(ConditionService conditionService) { this.conditionService = conditionService; } @Override public void run() { for(int i = 0;i<1000;i++){ conditionService.set(); } } } public class ThreadB extends Thread { private ConditionService conditionService; public ThreadB(ConditionService conditionService) { this.conditionService = conditionService; } @Override public void run() { for(int i = 0;i<1000;i++){ conditionService.get(); } } } public class Main { public static void main(String[] args) throws InterruptedException { ConditionService conditionService = new ConditionService(); ThreadA a = new ThreadA(conditionService); a.start(); ThreadB b = new ThreadB(conditionService); b.start(); } } 黑色 白色 黑色 白色 ...