5. wait/notify
5.1 原理

- 锁对象调用wait方法(obj.wait()),就会使当前线程进入 WaitSet 中,变为 WAITING 状态。
- 处于BLOCKED和 WAITING 状态的线程都为阻塞状态,CPU 都不会分给他们时间片。但是有所区别:
- BLOCKED 状态的线程是在竞争对象时,发现 Monitor 的 Owner 已经是别的线程了,此时就会进入 EntryList 中,并处于 BLOCKED 状态
- WAITING 状态的线程是获得了对象的锁,但是自身因为某些原因需要进入阻塞状态时,锁对象调用了 wait 方法而进入了 WaitSet 中,处于 WAITING 状态
- BLOCKED 状态的线程会在锁被释放的时候被唤醒,但是处于 WAITING 状态的线程只有被锁对象调用了 notify 方法(obj.notify/obj.notifyAll),才会被唤醒。
注:只有当对象加锁以后,才能调用 wait 和 notify 方法
代码示例:
@Slf4j(topic = "WaitTest")public class WaitTest {private static final Object object = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (object){log.debug("t1执行...");try {object.wait(); //让t1进入obj的等待队列waitinglog.debug("t1继续执行....");} catch (InterruptedException e) {e.printStackTrace();}}}, "t1");Thread t2 = new Thread(()->{synchronized (object){log.debug("t2执行...");try {object.wait(); //让t2进入obj的等待队列waitinglog.debug("t2继续执行....");} catch (InterruptedException e) {e.printStackTrace();}}}, "t2");t1.start();t2.start();// 主线程try {Thread.sleep(2000); // 睡两秒synchronized (object){log.debug("唤醒线程");// 唤醒任意一个在obj上等待的线程object.notify();}} catch (InterruptedException e) {e.printStackTrace();}}}
13:28:11.595 [t1] DEBUG WaitTest - t1执行...13:28:11.599 [t2] DEBUG WaitTest - t2执行...13:28:13.597 [main] DEBUG WaitTest - 唤醒线程13:28:13.598 [t1] DEBUG WaitTest - t1继续执行....
主线程随机唤醒了t1线程
代码示例2,带参数的wait
@Slf4j(topic = "WaitTest")public class WaitTest {private static final Object object = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (object){log.debug("t1执行...");try {object.wait(2000); //让t1进入obj的等待队列waitinglog.debug("t1继续执行....");} catch (InterruptedException e) {e.printStackTrace();}}}, "t1");t1.start();// 主线程try {Thread.sleep(1000); // 睡1秒synchronized (object){log.debug("唤醒线程");// 唤醒任意一个在obj上等待的线程object.notify();}} catch (InterruptedException e) {e.printStackTrace();}}}
13:34:24.231 [t1] DEBUG WaitTest - t1执行...13:34:25.233 [main] DEBUG WaitTest - 唤醒线程13:34:25.233 [t1] DEBUG WaitTest - t1继续执行....
5.2 如何正确使用这两个方法
5.2.1 sleep与wait的区别
- Sleep 是 Thread 类的静态方法,Wait 是 Object 的方法,Object 又是所有类的父类,所以所有类都有Wait方法。
- Sleep 在阻塞的时候不会释放锁,而 Wait 在阻塞的时候会释放锁,它们都会释放 CPU 资源。
- Sleep 不需要与 synchronized 一起使用,而 Wait 需要与 synchronized 一起使用(对象被锁以后才能使用)
- 使用 wait 一般需要搭配 notify 或者 notifyAll 来使用,不然会让线程一直等待。
使用后线程的状态都是TIMED_WAITING
代码示例:
sleep方法: ```java @Slf4j(topic = “c.test4”) public class Test4 {
private final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()-> {synchronized (obj){try {log.debug("t1拿到锁");Thread.sleep(5000); //睡5秒} catch (InterruptedException e) {e.printStackTrace();}}},"t1").start();Thread.sleep(1000); //主线程睡1秒synchronized (obj){log.debug("主线程拿到锁");}
} }
```java13:48:07.151 [t1] DEBUG c.test4 - t1拿到锁13:48:12.159 [main] DEBUG c.test4 - 主线程拿到锁
可以看到,t1线程拿着锁,睡了5秒之后主线程才能拿到锁,t1在睡眠中无法拿到锁。
wait方法 ```java @Slf4j(topic = “c.test4”) public class Test4 {
private final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()-> {synchronized (obj){try {log.debug("t1拿到锁");//Thread.sleep(5000); //睡5秒obj.wait(5000); //等待5秒} catch (InterruptedException e) {e.printStackTrace();}}},"t1").start();Thread.sleep(1000); //主线程睡1秒synchronized (obj){log.debug("主线程拿到锁");}
} }
```java13:49:58.837 [t1] DEBUG c.test4 - t1拿到锁13:49:59.839 [main] DEBUG c.test4 - 主线程拿到锁
可以看到主线程在睡1秒后就能立即拿到锁,说明t1线程使用wait方法后释放了锁。
5.2.2 如何优雅使用
代码举例:
@Slf4j(topic = "c.Test5")public class Test5 {private final static Object room = new Object();private static boolean hasCigarette = false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?{}", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?{}", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小红").start();for (int i = 0; i < 5; i++) {new Thread(()->{synchronized (room){log.debug("其他人开始干活了");}}, "其他人").start();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{//synchronized (room){log.debug("烟送到了");hasCigarette = true;//}},"送烟").start();}}
14:27:10.934 [小红] DEBUG c.Test5 - 有烟没?false14:27:10.940 [小红] DEBUG c.Test5 - 没烟,先歇会14:27:11.937 [送烟] DEBUG c.Test5 - 烟送到了14:27:12.945 [小红] DEBUG c.Test5 - 有烟没?true14:27:12.945 [小红] DEBUG c.Test5 - 可以开始干活了14:27:12.946 [其他人] DEBUG c.Test5 - 其他人开始干活了14:27:12.946 [其他人] DEBUG c.Test5 - 其他人开始干活了14:27:12.946 [其他人] DEBUG c.Test5 - 其他人开始干活了14:27:12.946 [其他人] DEBUG c.Test5 - 其他人开始干活了14:27:12.947 [其他人] DEBUG c.Test5 - 其他人开始干活了
问题,小红在睡眠的时候其他线程也无法工作,而且,就算烟提前送过来了,小红依然等睡够了才工作。可以在送烟线程打断小红的睡眠,但是这样做并不好。小红一直持有者锁,其他线程一直进不来,只有小红干完活才行,效率低。如果在送烟出加上同步锁,那他没法送烟,锁一直被小红持有。
- 改进版 ```java @Slf4j(topic = “c.Test5”) public class Test5 { private final static Object room = new Object(); private static boolean hasCigarette = false;
public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?{}", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?{}", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小红").start();for (int i = 0; i < 5; i++) {new Thread(()->{synchronized (room){log.debug("其他人开始干活了");}}, "其他人").start();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{synchronized (room){log.debug("烟送到了");hasCigarette = true;// 唤醒等待的room.notify();}},"送烟").start();}
}
```java14:34:37.881 [小红] DEBUG c.Test5 - 有烟没?false14:34:37.886 [小红] DEBUG c.Test5 - 没烟,先歇会14:34:37.887 [其他人] DEBUG c.Test5 - 其他人开始干活了14:34:37.887 [其他人] DEBUG c.Test5 - 其他人开始干活了14:34:37.887 [其他人] DEBUG c.Test5 - 其他人开始干活了14:34:37.887 [其他人] DEBUG c.Test5 - 其他人开始干活了14:34:37.888 [其他人] DEBUG c.Test5 - 其他人开始干活了14:34:38.882 [送烟] DEBUG c.Test5 - 烟送到了14:34:38.883 [小红] DEBUG c.Test5 - 有烟没?true14:34:38.883 [小红] DEBUG c.Test5 - 可以开始干活了
在小红休息的同时,其他人也可以干活,效率提升,解决了其他干活线程的阻塞问题。如果有其他线程在等待?
虚假唤醒(两个等待线程)
@Slf4j(topic = "c.Test5")public class Test5 {private final static Object room = new Object();private static boolean hasCigarette = false;private static boolean hasToke= false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?{}", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?{}", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小红").start();new Thread(() -> {synchronized (room) {log.debug("外卖送到没?{}", hasToke);if (!hasToke) {log.debug("没外卖,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?{}", hasToke);if (hasToke) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小蓝").start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{synchronized (room){log.debug("外卖送到了");hasToke = true;// 唤醒等待的room.notify();}},"送外卖").start();}}
14:40:14.297 [小红] DEBUG c.Test5 - 有烟没?false14:40:14.302 [小红] DEBUG c.Test5 - 没烟,先歇会14:40:14.302 [小蓝] DEBUG c.Test5 - 外卖送到没?false14:40:14.302 [小蓝] DEBUG c.Test5 - 没外卖,先歇会14:40:15.301 [送外卖] DEBUG c.Test5 - 外卖送到了14:40:15.301 [小红] DEBUG c.Test5 - 有烟没?false14:40:15.302 [小红] DEBUG c.Test5 - 没干成活
因为notify是随机叫醒的线程,没有叫醒小蓝。
使用notifyAll唤醒所有线程,解决了部分线程的虚假唤醒问题。 ```java @Slf4j(topic = “c.Test5”) public class Test5 { private final static Object room = new Object(); private static boolean hasCigarette = false; private static boolean hasToke= false;
public static void main(String[] args) {
new Thread(() -> {synchronized (room) {log.debug("有烟没?{}", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?{}", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小红").start();new Thread(() -> {synchronized (room) {log.debug("外卖送到没?{}", hasToke);if (!hasToke) {log.debug("没外卖,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?{}", hasToke);if (hasToke) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小蓝").start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{synchronized (room){log.debug("外卖送到了");hasToke = true;// 唤醒等待的room.notifyAll();}},"送外卖").start();
} }
```java14:45:44.905 [小红] DEBUG c.Test5 - 有烟没?false14:45:44.909 [小红] DEBUG c.Test5 - 没烟,先歇会14:45:44.910 [小蓝] DEBUG c.Test5 - 外卖送到没?false14:45:44.910 [小蓝] DEBUG c.Test5 - 没外卖,先歇会14:45:45.906 [送外卖] DEBUG c.Test5 - 外卖送到了14:45:45.906 [小蓝] DEBUG c.Test5 - 外卖送到没?true14:45:45.906 [小蓝] DEBUG c.Test5 - 可以开始干活了14:45:45.907 [小红] DEBUG c.Test5 - 有烟没?false14:45:45.907 [小红] DEBUG c.Test5 - 没干成活
最终版:将不满足条件的if改成while可以解决这个问题。
@Slf4j(topic = "c.Test5")public class Test5 {private final static Object room = new Object();private static boolean hasCigarette = false;private static boolean hasToke= false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?{}", hasCigarette);while (!hasCigarette) {log.debug("没烟,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?{}", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小红").start();new Thread(() -> {synchronized (room) {log.debug("外卖送到没?{}", hasToke);while (!hasToke) {log.debug("没外卖,先歇会");try {//Thread.sleep(2000);room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?{}", hasToke);if (hasToke) {log.debug("可以开始干活了");}else {log.debug("没干成活");}}}, "小蓝").start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{synchronized (room){log.debug("外卖送到了");hasToke = true;// 唤醒等待的room.notifyAll();}},"送外卖").start();}}
14:49:19.721 [小红] DEBUG c.Test5 - 有烟没?false14:49:19.725 [小红] DEBUG c.Test5 - 没烟,先歇会14:49:19.725 [小蓝] DEBUG c.Test5 - 外卖送到没?false14:49:19.725 [小蓝] DEBUG c.Test5 - 没外卖,先歇会14:49:20.724 [送外卖] DEBUG c.Test5 - 外卖送到了14:49:20.725 [小蓝] DEBUG c.Test5 - 外卖送到没?true14:49:20.725 [小蓝] DEBUG c.Test5 - 可以开始干活了14:49:20.725 [小红] DEBUG c.Test5 - 没烟,先歇会
总结:
什么时候适合使用wait
- 当线程不满足某些条件,需要暂停运行时,可以使用 wait 。这样会将对象的锁释放,让其他线程能够继续运行。如果此时使用 sleep,会导致所有线程都进入阻塞,导致所有线程都没法运行,直到当前线程 sleep 结束后,运行完毕,才能得到执行。
使用wait/notify需要注意什么
- 当有多个线程在运行时,对象调用了 wait 方法,此时这些线程都会进入 WaitSet 中等待。如果这时使用了 notify 方法,可能会造成虚假唤醒(唤醒的不是满足条件的等待线程),这时就需要使用 notifyAll 方法 ```java synchronized (lock) { while(//不满足条件,一直等待,避免虚假唤醒) { lock.wait(); } //满足条件后再运行 }
synchronized (lock) { //唤醒所有等待线程 lock.notifyAll(); }
<a name="M5CJV"></a>## 5.3 同步模式——保护性暂停即 Guarded Suspension,用在一个线程等待另一个线程的执行结果,要点:- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)- JDK 中,join 的实现、Future 的实现,采用的就是此模式- 因为要等待另一方的结果,因此归类到同步模式<a name="QALtc"></a>### 5.3.1 基本实现> 基本实现```java@Slf4j(topic = "c.Test6")public class Test6 {public static void main(String[] args) {// 线程1等待线程2的结果GuardedObject object = new GuardedObject();new Thread(() -> {log.debug("等待t2结果");Object o = object.getObject();// 拿到结果后打印log.debug("拿到结果,结果为:{}", o);}, "t1").start();new Thread(() -> {log.debug("产生结果");try {Thread.sleep(3000);ArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(5);arrayList.add(6);log.debug("结果产生完毕");object.complete(arrayList);} catch (InterruptedException e) {e.printStackTrace();}}, "t2").start();}}class GuardedObject {private Object object;// 获取结果public Object getObject() {synchronized (this) {while (object == null) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}return object;}}// 产生结果public void complete(Object object) {synchronized (this) {// 给成员变量赋值this.object = object;this.notify();}}}
21:05:31.610 [t2] DEBUG c.Test6 - 产生结果21:05:31.610 [t1] DEBUG c.Test6 - 等待t2结果21:05:34.618 [t2] DEBUG c.Test6 - 结果产生完毕21:05:34.619 [t1] DEBUG c.Test6 - 拿到结果,结果为:[5, 6]
- 避免了join必须等到线程结束才能干别的事情,线程2产生完结果之后还可干别的事情 ;使用join等待结果的变量必须是全局变量,而该方法可以是局部变量。
5.3.2 扩展
拓展:为线程1增加超时等待,超时之后不管有没有结果就退出循环
@Slf4j(topic = "c.Test6")public class Test6 {public static void main(String[] args) {// 线程1等待线程2的结果GuardedObject object = new GuardedObject();new Thread(() -> {log.debug("等待t2结果");Object object1 = object.getObject(1000);// 拿到结果后打印log.debug("拿到结果,结果为:{}", object1);}, "t1").start();new Thread(() -> {log.debug("产生结果");try {Thread.sleep(4000);ArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(5);arrayList.add(6);log.debug("结果产生完毕");object.complete(arrayList);} catch (InterruptedException e) {e.printStackTrace();}}, "t2").start();}}class GuardedObject {// 结果private Object object;// 获取结果public Object getObject(long timeout) {synchronized (this) {// 开始时间long beginTime = System.currentTimeMillis();// 经历时间long passedTime = 0;while (object == null) {// 这一轮循环应该等待的时间long waitTime = timeout - passedTime;// 如果当前等待时间<=0,退出循环if (waitTime <= 0) {break;}try {// 避免虚假唤醒,引起多等待的时间this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}// 计算经历时间passedTime = System.currentTimeMillis() - beginTime;}return object;}}// 产生结果public void complete(Object object) {synchronized (this) {// 给成员变量赋值this.object = object;this.notify();}}}
21:03:50.959 [t1] DEBUG c.Test6 - 等待t2结果21:03:50.959 [t2] DEBUG c.Test6 - 产生结果21:03:51.967 [t1] DEBUG c.Test6 - 拿到结果,结果为:null21:03:54.964 [t2] DEBUG c.Test6 - 结果产生完毕
可以看到,线程1等待一秒之后就不再等了,这个时候结果还没有产出,所以为null。
虚假唤醒测试 ```java @Slf4j(topic = “c.Test6”) public class Test6 {
public static void main(String[] args) {
// 线程1等待线程2的结果GuardedObject object = new GuardedObject();new Thread(() -> {log.debug("等待t2结果");Object object1 = object.getObject(2000);// 拿到结果后打印log.debug("拿到结果,结果为:{}", object1);}, "t1").start();new Thread(() -> {
log.debug("产生结果");try {Thread.sleep(1000);ArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(5);arrayList.add(6);log.debug("结果产生完毕");object.complete(null);} catch (InterruptedException e) {e.printStackTrace();}}, "t2").start();}
}
class GuardedObject { // 结果 private Object object;
// 获取结果public Object getObject(long timeout) {synchronized (this) {// 开始时间long beginTime = System.currentTimeMillis();// 经历时间long passedTime = 0;while (object == null) {// 这一轮循环应该等待的时间long waitTime = timeout - passedTime;// 如果当前等待时间<=0,退出循环if (waitTime <= 0) {break;}try {// 避免虚假唤醒,引起多等待的时间this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}// 计算经历时间passedTime = System.currentTimeMillis() - beginTime;}return object;}}// 产生结果public void complete(Object object) {synchronized (this) {// 给成员变量赋值this.object = object;this.notify();}}
}
```java21:08:07.833 [t1] DEBUG c.Test6 - 等待t2结果21:08:07.833 [t2] DEBUG c.Test6 - 产生结果21:08:08.843 [t2] DEBUG c.Test6 - 结果产生完毕21:08:09.842 [t1] DEBUG c.Test6 - 拿到结果,结果为:null
可以看到,t2线程1s后产生了结果,但是并没有赋值,虚假唤醒了t1线程,但是t1线程依然在2s后拿结果而不是变成了3s。
5.3.3 join()原理
底层就是保护性暂停。
public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}
5.3.4 拓展2
多任务版 GuardedObject 图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右侧的 t1,t3,t5 就好比邮递员。 如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。和生产者消费者模式的区别就是:这个生产者和消费者之间是一一对应的关系,但是生产者消费者模式并不是。rpc 框架的调用中就使用到了这种模式。
@Slf4j(topic = "c.Test6")public class Test6 {public static void main(String[] args) throws InterruptedException {// 居民取信for (int i = 0; i < 3; i++) {new People().start();}Thread.sleep(1000);// 快递员送信for (Integer id : MailBox.getIds()) {new PostMan(id, "内容" + id).start();}}}// 邮箱class MailBox {private static Map<Integer, GuardedObject> map = new Hashtable<>();private static int id = 1;// 产生唯一idprivate synchronized static int generateId() {return id++;}// 新建邮箱格子public static GuardedObject createGuardedObject() {GuardedObject guardedObject = new GuardedObject(generateId());// 放入邮箱中map.put(guardedObject.getId(), guardedObject);return guardedObject;}// 查询所有邮件idpublic static Set<Integer> getIds() {return map.keySet();}// 通过id获取邮件格子public static GuardedObject getGuardedObject(Integer id) {return map.remove(id);}}// 取信@Slf4j(topic = "c.people")class People extends Thread {@Overridepublic void run() {GuardedObject guardedObject = MailBox.createGuardedObject();log.debug("开始取信 id {}", guardedObject.getId());Object mail = guardedObject.getObject(5000);log.debug("取到信id {},内容{}", guardedObject.getId(), mail);}}// 送信@Slf4j(topic = "c.postMan")class PostMan extends Thread {private int id;private String mail;public PostMan(int id, String mail) {this.id = id;this.mail = mail;}@Overridepublic void run() {// 送信GuardedObject guardedObject = MailBox.getGuardedObject(id);log.debug("送信id:{}", id);guardedObject.complete(mail);}}// 充当邮箱中的格子class GuardedObject {private int id;public GuardedObject() {}public GuardedObject(int id) {this.id = id;}public int getId() {return id;}// 结果private Object object;// 获取结果public Object getObject(long timeout) {synchronized (this) {// 开始时间long beginTime = System.currentTimeMillis();// 经历时间long passedTime = 0;while (object == null) {// 这一轮循环应该等待的时间long waitTime = timeout - passedTime;// 如果当前等待时间<=0,退出循环if (waitTime <= 0) {break;}try {// 避免虚假唤醒,引起多等待的时间this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}// 计算经历时间passedTime = System.currentTimeMillis() - beginTime;}return object;}}// 产生结果public void complete(Object object) {synchronized (this) {// 给成员变量赋值this.object = object;this.notify();}}}
21:53:18.345 [Thread-0] DEBUG c.people - 开始取信 id 221:53:18.345 [Thread-1] DEBUG c.people - 开始取信 id 321:53:18.345 [Thread-2] DEBUG c.people - 开始取信 id 121:53:19.345 [Thread-4] DEBUG c.postMan - 送信id:221:53:19.345 [Thread-3] DEBUG c.postMan - 送信id:321:53:19.345 [Thread-5] DEBUG c.postMan - 送信id:121:53:19.345 [Thread-2] DEBUG c.people - 取到信id 1,内容内容121:53:19.345 [Thread-0] DEBUG c.people - 取到信id 2,内容内容221:53:19.345 [Thread-1] DEBUG c.people - 取到信id 3,内容内容3
5.4 异步模式——生产者和消费者
- 与前面的保护性暂停中的 GuardObject 不同,不需要产生结果和消费结果的线程一一对应消费队列可以用来平衡生产和消费的线程资源
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK 中各种阻塞队列,采用的就是这种模式
6. park与unpark
6.1 基本使用
park & unpark 是 LockSupport 线程通信工具类的静态方法。
// 暂停当前线程LockSupport.park();// 恢复某个线程的运行LockSupport.unpark(线程);
特点:
- wait、notify、notifyAll必须配合Object Monitor使用,park与unpark不必配合
- park与unpark是以线程单位来【阻塞】和【唤醒】线程,而notify只能唤醒随机一个等待线程,notifyAll唤醒所有等待线程,不那么精确。
park与unpark可以先unpark,而wait和notify不能先notify
6.2 实现原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter, _cond 和 _mutex
打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量 _ cond 就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息(这种情况是先调用park,没有粮食)
- 如果备用干粮充足,那么不需停留,继续前进(这种情况是先调用unpark补充粮食了)
- 调用 unpark,就好比令干粮充足
- 当前线程调用 Unsafe.park() 方法
- 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁(mutex对象有个等待队列 _cond)
- 线程进入 _cond 条件变量阻塞
- 设置 _counter = 0

调用 upark
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 唤醒 _cond 条件变量中的 Thread_0
- Thread_0 恢复运行
- 设置 _counter 为 0
6.2.2 先unpark后park
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 当前线程调用 Unsafe.park() 方法
- 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
- 设置 _counter 为 0
7. 重新理解线程状态转换

- 情况一:NEW –> RUNNABLE
- 当调用了 t.start() 方法时,由 NEW –> RUNNABLE
情况二: RUNNABLE <–> WAITING
- 当调用了t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
调用 obj.notify() , obj.notifyAll() , t.interrupt() 时,会在 WaitSet 等待队列中出现锁竞争,非公平竞争
- 竞争锁成功,t 线程从 WAITING –> RUNNABLE
竞争锁失败,t 线程从 WAITING –> BLOCKED ```java @Slf4j(topic = “c.Test8”) public class Test8 { public static void main(String[] args) { Object object = new Object(); new Thread(()->{
synchronized (object){log.debug("t1..wait..");try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1剩下的");}
}, “t1”).start(); new Thread(()->{
synchronized (object){log.debug("t2..wait..");try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug("t2剩下的");}
}, “t2”).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} synchronized (object){
object.notifyAll();
} } }
- **情况三:RUNNABLE <–> WAITING**- 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING- 注意是当前线程在 t 线程对象的监视器上等待- t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE- **情况四: RUNNABLE <–> WAITING**- 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –> RUNNABLE- **情况五: RUNNABLE <–> TIMED_WAITING**- t 线程用 synchronized(obj) 获取了对象锁后- 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING- t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时- 竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE- 竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED- **情况六:RUNNABLE <–> TIMED_WAITING**- 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING注意是当前线程在 t 线程对象的监视器上等待- 当前线程等待时间超过了 n 毫秒,或 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING –> RUNNABLE- **情况七:RUNNABLE <–> TIMED_WAITING**- 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING- 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE- **情况八:RUNNABLE <–> TIMED_WAITING**- 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE –> TIMED_WAITING- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE- **情况九:RUNNABLE <–> BLOCKED**- t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED- **情况十: RUNNABLE <–> TERMINATED**- 当前线程所有代码运行完毕,进入 TERMINATED<a name="CBbfZ"></a># 8. 多把锁资源之间互相是没有关联的,可以将锁的粒度细分,增强并发度;一个线程同时获得多把锁也会有死锁的风险。> 代码示例:```java@Slf4j(topic = "c.Test9")public class Test9 {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();// 小红睡觉, 小蓝学习,为了保证其互不干扰,所以用不同的锁new Thread(()->{bigRoom.sleep();}, "小红").start();// 小红睡觉, 小蓝学习,为了保证其互不干扰,所以用不同的锁new Thread(()->{bigRoom.study();}, "小蓝").start();}}@Slf4j(topic = "c.BigRoom")class BigRoom{private final Object bedroom = new Object();private final Object studyRoom = new Object();// 睡觉用bedroom锁public void sleep() {synchronized (bedroom){log.debug("bedroom2个小时");// 睡觉try {Thread.sleep(2000);log.debug("睡醒了");} catch (InterruptedException e) {e.printStackTrace();}}}// 学习用studyRoom锁public void study(){synchronized (studyRoom){log.debug("study1个小时");//学习try {Thread.sleep(1000);log.debug("学完了");} catch (InterruptedException e) {e.printStackTrace();}}}}
14:07:44.651 [小红] DEBUG c.BigRoom - bedroom2个小时14:07:44.651 [小蓝] DEBUG c.BigRoom - study1个小时14:07:45.655 [小蓝] DEBUG c.BigRoom - 学完了14:07:46.658 [小红] DEBUG c.BigRoom - 睡醒了
9.活跃性
线程因为某些原因,导致代码一直无法执行完毕,这种的现象叫做活跃性。
9.1 死锁
9.1.1 死锁现象
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。如:t1 线程获得 A 对象锁,接下来想获取 B 对象的锁 ;t2 线程获得 B 对象锁,接下来想获取 A 对象的锁。即两个线程在持有自己的锁的同时都想再持有对方的锁。
@Slf4j(topic = "c.Test10")public class Test10 {public static void main(String[] args) {final Object A = new Object();final Object B = new Object();new Thread(()->{synchronized (A) {log.debug("lock A...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (B) {log.debug("lock B...");log.debug("操作...");}}}).start();new Thread(()->{synchronized (B) {try {log.debug("lock B...");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (A) {log.debug("lock A...");log.debug("操作...");}}}).start();}}
14:18:47.133 [Thread-0] DEBUG c.Test10 - lock A...14:18:47.133 [Thread-1] DEBUG c.Test10 - lock B...
9.1.2 死锁监控
使用jps命令查看所有线程, 使用jstack 线程号 查看线程详细信息
2021-05-09 14:19:19Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):"Attach Listener" #14 daemon prio=9 os_prio=31 tid=0x00007fe0688f7000 nid=0x5703 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"DestroyJavaVM" #13 prio=5 os_prio=31 tid=0x00007fe06a1a5000 nid=0xd03 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Thread-1" #12 prio=5 os_prio=31 tid=0x00007fe06a0e2800 nid=0x5603 waiting for monitor entry [0x0000700003778000]java.lang.Thread.State: BLOCKED (on object monitor)at com.ll.ch3.Test10.lambda$main$1(Test10.java:34)- waiting to lock <0x000000076b91da28> (a java.lang.Object)- locked <0x000000076b91da38> (a java.lang.Object)at com.ll.ch3.Test10$$Lambda$2/1164175787.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Thread-0" #11 prio=5 os_prio=31 tid=0x00007fe06a0e1800 nid=0xa403 waiting for monitor entry [0x0000700003675000]java.lang.Thread.State: BLOCKED (on object monitor)at com.ll.ch3.Test10.lambda$main$0(Test10.java:19)- waiting to lock <0x000000076b91da38> (a java.lang.Object)- locked <0x000000076b91da28> (a java.lang.Object)at com.ll.ch3.Test10$$Lambda$1/1908923184.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007fe06a069800 nid=0xa803 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007fe06c034800 nid=0x5503 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fe06900a000 nid=0x3f03 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fe06f020000 nid=0x4103 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fe06a068800 nid=0x4203 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fe06a04e000 nid=0x3c03 runnable [0x0000700002f60000]java.lang.Thread.State: RUNNABLEat java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)at java.net.SocketInputStream.read(SocketInputStream.java:171)at java.net.SocketInputStream.read(SocketInputStream.java:141)at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)- locked <0x000000076ac9c598> (a java.io.InputStreamReader)at java.io.InputStreamReader.read(InputStreamReader.java:184)at java.io.BufferedReader.fill(BufferedReader.java:161)at java.io.BufferedReader.readLine(BufferedReader.java:324)- locked <0x000000076ac9c598> (a java.io.InputStreamReader)at java.io.BufferedReader.readLine(BufferedReader.java:389)at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fe06c033800 nid=0x3a03 runnable [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fe068812000 nid=0x3303 in Object.wait() [0x0000700002c54000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x000000076ab08ed8> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)- locked <0x000000076ab08ed8> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fe068811800 nid=0x4d03 in Object.wait() [0x0000700002b51000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"VM Thread" os_prio=31 tid=0x00007fe06a033800 nid=0x4e03 runnable"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fe068016000 nid=0x1d07 runnable"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fe068016800 nid=0x2103 runnable"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fe06c008800 nid=0x1e03 runnable"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fe06a00a000 nid=0x2a03 runnable"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007fe06a00b000 nid=0x2c03 runnable"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007fe06a00b800 nid=0x2e03 runnable"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007fe068017000 nid=0x5403 runnable"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007fe068017800 nid=0x5203 runnable"GC task thread#8 (ParallelGC)" os_prio=31 tid=0x00007fe06c009000 nid=0x3003 runnable"GC task thread#9 (ParallelGC)" os_prio=31 tid=0x00007fe06c009800 nid=0x5003 runnable"VM Periodic Task Thread" os_prio=31 tid=0x00007fe068024800 nid=0xa603 waiting on conditionJNI global references: 319Found one Java-level deadlock: 找到了死锁============================="Thread-1":waiting to lock monitor 0x00007fe06a80f238 (object 0x000000076b91da28, a java.lang.Object),which is held by "Thread-0""Thread-0":waiting to lock monitor 0x00007fe06a811078 (object 0x000000076b91da38, a java.lang.Object),which is held by "Thread-1"Java stack information for the threads listed above:==================================================="Thread-1":at com.ll.ch3.Test10.lambda$main$1(Test10.java:34) 34行- waiting to lock <0x000000076b91da28> (a java.lang.Object) 等待锁- locked <0x000000076b91da38> (a java.lang.Object) 自身锁住的对象at com.ll.ch3.Test10$$Lambda$2/1164175787.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)"Thread-0":at com.ll.ch3.Test10.lambda$main$0(Test10.java:19) 19行- waiting to lock <0x000000076b91da38> (a java.lang.Object) 等待锁- locked <0x000000076b91da28> (a java.lang.Object) 自身锁住的对象at com.ll.ch3.Test10$$Lambda$1/1908923184.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.(base) lilei@lileideMacBookPro ~ %
9.1.3 发生死锁的必要条件
- 互斥条件
在一段时间内,一种资源只能被一个进程所使用
- 请求和保持条件
进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞 状态,并且不释放自己已有的资源
- 不可抢占条件
进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放
- 循环等待条件
9.1.4 哲学家进餐问题
有五位哲学家,围坐在圆桌旁。 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。 如果筷子被身边的人拿着,自己就得等待 。当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况。
package com.ll.ch3;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test11")public class Test11 {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("哲学家1", c1, c2).start();new Philosopher("哲学家2", c2, c3).start();new Philosopher("哲学家3", c3, c4).start();new Philosopher("哲学家4", c4, c5).start();new Philosopher("哲学家5", c5, c1).start();}}class Chopstick {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" +"name='" + name + '\'' +'}';}}@Slf4j(topic = "c.Philosopher")class Philosopher extends Thread {Chopstick left;Chopstick right;public Philosopher(String name ,Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true){// 左手拿筷子synchronized (left){// 右手拿筷子synchronized (right){eat();}}}}private void eat() {log.debug("eat");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
14:35:39.034 [哲学家1] DEBUG c.Philosopher - eat14:35:39.034 [哲学家3] DEBUG c.Philosopher - eat14:35:40.038 [哲学家3] DEBUG c.Philosopher - eat14:35:40.038 [哲学家1] DEBUG c.Philosopher - eat14:35:41.043 [哲学家1] DEBUG c.Philosopher - eat14:35:41.043 [哲学家3] DEBUG c.Philosopher - eat14:35:42.044 [哲学家3] DEBUG c.Philosopher - eat14:35:42.044 [哲学家1] DEBUG c.Philosopher - eat14:35:43.045 [哲学家3] DEBUG c.Philosopher - eat14:35:44.046 [哲学家3] DEBUG c.Philosopher - eat14:35:45.049 [哲学家3] DEBUG c.Philosopher - eat14:35:46.053 [哲学家3] DEBUG c.Philosopher - eat// 卡住了
出现死锁
Found one Java-level deadlock:============================="哲学家5":waiting to lock monitor 0x00007fe80a17b768 (object 0x000000076b920fd8, a com.ll.ch3.Chopstick),which is held by "哲学家1""哲学家1":waiting to lock monitor 0x00007fe80a00f158 (object 0x000000076b920fe8, a com.ll.ch3.Chopstick),which is held by "哲学家2""哲学家2":waiting to lock monitor 0x00007fe80a00f0a8 (object 0x000000076b920ff8, a com.ll.ch3.Chopstick),which is held by "哲学家3""哲学家3":waiting to lock monitor 0x00007fe80a17b608 (object 0x000000076b921008, a com.ll.ch3.Chopstick),which is held by "哲学家4""哲学家4":waiting to lock monitor 0x00007fe80a17b558 (object 0x000000076b921018, a com.ll.ch3.Chopstick),which is held by "哲学家5"Java stack information for the threads listed above:==================================================="哲学家5":at com.ll.ch3.Philosopher.run(Test11.java:55)- waiting to lock <0x000000076b920fd8> (a com.ll.ch3.Chopstick)- locked <0x000000076b921018> (a com.ll.ch3.Chopstick)"哲学家1":at com.ll.ch3.Philosopher.run(Test11.java:55)- waiting to lock <0x000000076b920fe8> (a com.ll.ch3.Chopstick)- locked <0x000000076b920fd8> (a com.ll.ch3.Chopstick)"哲学家2":at com.ll.ch3.Philosopher.run(Test11.java:55)- waiting to lock <0x000000076b920ff8> (a com.ll.ch3.Chopstick)- locked <0x000000076b920fe8> (a com.ll.ch3.Chopstick)"哲学家3":at com.ll.ch3.Philosopher.run(Test11.java:55)- waiting to lock <0x000000076b921008> (a com.ll.ch3.Chopstick)- locked <0x000000076b920ff8> (a com.ll.ch3.Chopstick)"哲学家4":at com.ll.ch3.Philosopher.run(Test11.java:55)- waiting to lock <0x000000076b921018> (a com.ll.ch3.Chopstick)- locked <0x000000076b921008> (a com.ll.ch3.Chopstick)Found 1 deadlock.
9.2 活锁
活锁出现在两个线程互相改变对方的结束条件,谁也无法结束。(比如一个线程让一个数++,一个线程让一个数—,最后谁也没办法达到终止条件)
- 避免活锁的方法
在线程执行时,中途给予不同的间隔时间即可。(增加随机睡眠时间)
- 死锁与活锁的区别
](https://blog.csdn.net/weixin_50280576/article/details/113033975)

