Java中wait()方法为什么要放在同步块中

如果wait()方法不在同步块中,代码的确会抛出IllegalMonitorStateExeception:

public class WaitInSyncBlockTest {

    @Test
    public void test() {
        try {
            new Object().wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果是:

 

Lost Wake-Up Problem

事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起。

这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。

假如我们有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:

生产者伪代码:

count+1;
notify();

消费者伪代码:

while(count<=0)
   wait()

count--

熟悉多线程的朋友一眼就能够看出来,这里面有问题。什么问题呢?

生产者是两个步骤:

  1. count+1;
  2. notify();

消费者也是两个步骤:

  1. 检查count值;
  2. 睡眠或者减一;

万一这些步骤混杂在一起呢?比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……

image.png

这就是所谓的lost wake up问题。

那么怎么解决这个问题呢?

现在我们应该就能够看到,问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。

这就是一种很常见的竞态条件。

很自然的想法是,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。

于是生产者的代码是:

tryLock()

count+1

notify()

releaseLock()

消费者的代码是:

tryLock()

while(count <= 0)
  wait()
  
count-1

releaseLock

注意的是,我这里将两者的两个操作都放进去了同步块中。

所以,我们可以总结到,为了避免出现这种lost wake up问题,在这种模型之下,总应该将我们的代码放进去的同步块中。

Java强制我们的wait()/notify()调用必须要在一个同步块中,就是不想让我们在不经意间出现这种lost wake up问题。

不仅仅是这两个方法,包括java.util.concurrent.locks.Condition的await()/signal()也必须要在同步块中:

    private ReentrantLock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    @Test
    public void test() {
        try {
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

image.png

准确的来说,即便是我们自己在实现自己的锁机制的时候,也应该要确保类似于wait()和notify()这种调用,要在同步块内,防止使用者出现lost wake up问题。

Java的这种检测是很严格的。它要求的是,一定要处于锁对象的同步块中。举例来说:

    private Object obj = new Object();

    private Object anotherObj = new Object();

    @Test
    public void produce() {
        synchronized (obj) {
            try {
//没有用obj.notify()
                anotherObj.notify();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

这样一样出现IllegalMonitorStateException。

oracle的官方解释

The Lost Wake-Up Problem

Calling pthread_cond_signal() or pthread_cond_broadcast() when the thread does not hold the mutex lock associated with the condition can lead to lost wake-up bugs.

A lost wake-up occurs when:

  • A thread calls pthread_cond_signal() or pthread_cond_broadcast().

  • And another thread is between the test of the condition and the call to pthread_cond_wait().

  • And no threads are waiting.

    The signal has no effect, and therefore is lost.
     

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值