前言
之前对这些方法一直是云里雾里,最近正好看到相关的东西,赶紧重新整理一波。
wait & notify
wait
这是Object的方法,不允许重写。总结一下它的特点:
- 带参数的是等待一定时间,不带参数的是无限等待
- a.wait() 的时候,当前线程一定是持有了a的锁,否则会报错;所以a.wait方法一般是在synchronized(a){}内部执行的(也可以是用其他方式获得锁)
所以,就有了一个有趣的问题:notify()会立刻释放锁么? - a.wait()的时候,当前线程会释放a的锁,让出cpu执行时间。但是当前线程持有的其他锁不会释放
notify
这也是Object的方法,它是用来唤醒在某个对象的monitor上等待的线程。
- notify的唤醒顺序是随机的吗?
- 不是,是取决于jvm的实现,Hotspot是FIFO
- 参考文章:notify()是随机唤醒线程么?
- notify唤醒线程之后,线程会立即执行吗?
- 不一定,取决于两个因素:(1)被唤醒的线程首先需要获取锁,如果不能获取到锁,也不会执行(2)被唤醒的线程获取到锁之后,如果操作系统没有调度到它,也不会执行。
- notify先于wait方法执行,会怎样?
- 不会报错,但是会让调用wait的线程得不到唤醒。。
- notifyAll 是怎么唤醒线程的?
- a.notifyAll是唤醒全部调用了a.wait等待锁的线程
- 唤醒线程是LIFO的,会先唤醒等待队列的最后一个线程,然后这些线程依次去唤醒其前面的线程(不确定)
为什么wait¬ify要用同步块
因为著名的Lost wake-up problem,这个问题本以为很难,但是看了Java 多线程中的「lost wake up 问题」瞬间理解。
等待池 & 锁池
调用a.wait之后,当前线程会进入到一个“等待池”,等待被唤醒;而被唤醒之后会进入到“锁池”,等待获取a的对象锁。
这两个概念可能不是那么流行,但是可以帮助我们理解线程执行wait方法之后发生了什么。而且notify()是随机唤醒线程么? 这篇文章里,我们看到第一次实验的结果中,线程虽然不是顺序执行,但也不是绝对随机,而是有一部分是倒序的。
用等待池和锁池两个概念就可以解释这个问题了。等待池的线程会被顺序唤醒,被唤醒之后进入到锁池队列,但是main线程这时候很有可能还在持有锁,导致被唤醒的线程无法执行。main线程就会继续唤醒线程加入到锁池里。当main线程不再持有锁,锁池里的线程开始抢到锁,就会开始执行。而根据这时候的倒序可以推测:锁池的顺序可能是LIFO,于是产生了线程的倒序执行。
参考知乎问答
sleep
这是Thread的方法,必须指定参数。用于线程睡眠一定时间。
- sleep期间会让出cpu吗?
- 当然,不然就太浪费CPU了。而且可以验证一下,线程sleep之后看一下其cpu使用率会很低
- sleep指定时间之后,会立即执行吗?
- 不一定,只是会参与调度,具体是否执行需要由操作系统决定
- sleep之后线程进入什么状态
- TIMED_WAITING(Thread.State类里有所有状态)
- 到时间之后会变为RUNNABLE状态等待调度。
- sleep之后会释放锁吗?
- 不会,即便是wait方法也只是释放调用wait方法的对象对应的锁。
LockSupport.park
这是JDK新提供的方法,可以使线程等待一段时间,或者永久等待,类似wait
- 会释放锁吗?
- 不会,和sleep一样,它没有锁的概念。
- Condition是锁生成的,它调用await的时候本质上也是调用LockSupport.park方法;它会释放对应的锁
- unpark先于park执行会怎样?
- park执行的时候就不会等待了,直接继续执行
- 底层实现
- 调用Unsafe.park,这是一个native方法
- 本质上是一个信号量,只会取值为0和1
- 多次调用unpark会发生什么
- 和单次调用unpark一样,把信号量置为1,只能唤醒一次park
- 下面的示例可以验证这个逻辑,t线程只能打印出1和2,也就是只被唤醒了一次。
public static void main(String[] args) throws InterruptedException, IOException {
CountDownLatch countDownLatch1 = new CountDownLatch(1);
CountDownLatch countDownLatch2 = new CountDownLatch(1);
Thread t = new Thread(() -> {
try {
countDownLatch2.countDown();
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
LockSupport.park();
System.out.println("2");
LockSupport.park();
System.out.println("3");
});
t.start();
countDownLatch2.await();
LockSupport.unpark(t);
LockSupport.unpark(t);
countDownLatch1.countDown();
}