notify能唤醒Sleep的线程么?浅析wait/notify

641 阅读4分钟

在开始之前我们先看一下线程的状态:

线程状态

在java中线程一共有6种状态。分别为:

  1. NEW : 线程实例化完成,没有调用start之前。
  2. RUNNING: 线程就绪和运行状态统一成为RUNNING。
  3. WAITING :sleep()、wait()、join()、LockSupport.park()
  4. TIME_WAITING:sleep(1000)、wait(1000)、join(1000)、LockSupport.parkUntil()
  5. BLOCKED: 线程锁互斥。

sleep 和 wait 都能够使线程进入 WAITING 状态,那么notify能够唤醒 sleep 线程么?

Sleep和Wait区别

sleep不会释放锁(会释放CPU时间片),在存在锁的情况下,线程会一直占有锁,其他线程无法获取。

wait会释放锁,允许其他线程进入同步方法。在调用notify唤醒后会重新去获取锁。

sleep可以在任意地方使用,没有限制。wait只能在同步方法中使用,依赖于锁。

wait和notify

wait和notify使用均依赖于锁,且锁的对象必须是同一对象,否则无法执行唤醒。

问题:生产,消费模型,可以在线程内操作锁对象么?

举例,一个生产者消费者模型:生产者线程对共享资源 count(Integer 类型) 加锁, 加锁成功后每次对count执行加1操作, count == 5 时,停止生产,释放锁,然后唤醒消费者。消费者线程获取共享资源count锁成功后每次对count进行减1,到0时,释放锁,唤醒生产者。在这种情况下,Integer既作为共享资源,又作为锁的对象。那么这种情况会不会有问题呢?

答:会有问题,生产者、消费者无法互相唤醒。

由于消费者是在count = 0 时执行 notify、wait操作,生产者是在 count = 5时执行 notidy、wait操作,因此两个 count 其实不是同一个对象,无法执行唤醒。

PS:这里需要说明一下 Integer、int属于值传递,非引用传递。

public static void main(String[] args){
    Integer i = 0;
    add(i);
    add(i);
    // 这里输出结果依然为 0
    System.out.println(i);
}

publiv static void add(Integer i){
    i++;
}

notify唤醒是随机唤醒一个线程。唤醒的范围是同一锁对象,所有wait的线程,那么sleep的可以唤醒么?

答:notify唤醒的是wait的线程,sleep无法被唤醒。

首先,wait/notify的前置条件是加锁成功后,并且是同一锁对象。因为sleep不会释放锁,所以其他线程不可能有机会获取锁执行notify。

notify同样依赖于锁,必须在同步块中执行。执行后会立刻释放锁嘛?notify一定能成功唤醒么?

答: notify在执行后不会立刻去唤醒,而是要等到notify同步块执行完以后才会去执行唤醒,类似于线程的interrupted。

wait/notify、await/signal、park/Unpark 关系

wait/notify 是基于synchorized实现的。

await/signal是基于Lock实现的(LookSupport),JUC中提供了Condition类await/signal来代替wait/notify,而Condition底层就是基于park/unpark做的线程通讯。不仅是Condition,AQS中也使用了park/unpark,可以理解为await/signal是park/unpark的外层封装。

wait/notify、await/signal 对比

wait/notify无法控制唤醒谁,随机唤醒(没有分组的概念)

await/signal可以,await/signal支持多个condition消费者一组,生产者一组,这样在多生产者消费者时可以确保唤醒的一定是分组内的。

最后

为什么wait/notify、await/signal都要依赖锁?没有锁的情况下是否可以使用呢?

答:没有锁的情况是不可以使用的。

可以试想,什么情况下才需要用到等待/唤醒场景呢? 以吃饭举例,到饭店吃饭,点完餐以后自己就进入等待状态(等着吃饭),需要等待服务员唤醒(餐好了通知),此类场景我们就需要用到等待、唤醒。 那么此时需要考虑,如果吃饭的人多了,那么我们怎么知道“餐好了”是谁的餐好了?服务员又怎么知道“餐好了我该叫哪位顾客”?现实场景中我们都是有座位号,或者取餐号,那么这个座位号、取餐号就是我们的锁,我获取到这把锁(其他顾客无法再获取),那么我与我点的餐之间就有了一个关联关系。服务员就可以通过这把锁来对我进行唤醒(1号的餐好了~)。