在JDK的官方的wait()方法的注释中明确表示线程可能被“虚假唤醒“,JDK也明确推荐使用while来判断状态信息。那么这种情况的发生的可能性有多大呢?
使用生产者消费者模型来说明,伪唤醒造成的后果是本来未被唤醒的线程被唤醒了,那么就破坏了生产者消费者中的判断条件,也就是例子中的while条件number == 0或者number == 1。最终导致的结果就死0和1不能交替出现。
JDK的两种同步方案均可能出现这种伪唤醒的问题(API说明明确表示会出现这种现象),这两种组合是synchronized+wait+notify和ReentrantLock+await+signal。下面的例子中,如果把while换成if,那么就0、1就不能交替出现,反制则会,例子中是100个线程进行增加,100个线程进行减少。
在Java并发编程书上面引用了Thinking In Java的一句话说,大概意思是:任何并发编程都是通过加锁来解决。其实JDK并发编程也是通过加锁解决,每个对象都有一个对象锁,并且有一个与这个锁相关的队列,来实现并发编程。区别在于加锁的粒度问题,读读可以并发(在读远大于写的场景下比较合适),其他三种情况不能并发。而关系型数据库也是利用这一思想,只不过做得更加彻底,除了写写不能并发,其他三种情况都能并发,这得益于MVCC模型。
public class Resource { public int number = 0; public synchronized void add() { while (number == 1) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number++; System.err.println(number + "-" + Thread.currentThread().getId()); notifyAll(); } public synchronized void minus() { while (number == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number--; System.err.println(number + "-" + Thread.currentThread().getId()); notifyAll(); } public static class AddThread implements Runnable { private Resource resource; public AddThread(Resource resource) { this.resource = resource; } @Override public void run() { for (;;) resource.add(); } } public static class MinusThread implements Runnable { private Resource resource; public MinusThread(Resource resource) { this.resource = resource; } @Override public void run() { for (;;) resource.minus(); } } public static void main(String[] args) { Resource resource = new Resource(); for (int i = 0; i < 100; i++) { new Thread(new AddThread(resource)).start(); new Thread(new MinusThread(resource)).start(); } } }
public class ResourceLock { private int number = 0; private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public int getNumber() { return this.number; } public void increase() { lock.lock(); try { while (number == 1) { condition.await(); } number++; System.err.println(number + "-" + Thread.currentThread().getId()); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public void decrease() { lock.lock(); try { while (number == 0) { condition.await(); } number--; System.err.println(number + "-" + Thread.currentThread().getId()); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } static class IncreaseThread implements Runnable { private ResourceLock resource; public IncreaseThread(ResourceLock resource) { this.resource = resource; } @Override public void run() { for (;;) resource.increase(); } } static class DecreaseThread implements Runnable { private ResourceLock resource; public DecreaseThread(ResourceLock resource) { this.resource = resource; } @Override public void run() { for (;;) resource.decrease(); } } public static void main(String[] args) throws Exception { ResourceLock resource = new ResourceLock(); ExecutorService es = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { es.submit(new IncreaseThread(resource)); es.submit(new DecreaseThread(resource)); } } }