📒博客首页:热爱编程的大李子 📒
🌞文章目的:共享对象之管程 🌞
🍄参考视频:深入学习Java并发编程🍄
🙏博主在学习阶段,如若发现问题,请告知,非常感谢🙏
💙同时也非常感谢各位小伙伴们的支持💙
🌈每日一语:未经一番寒彻骨,哪得梅花扑鼻香!🌈
💗感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们💗
🌟最后,祝大家每天进步亿点点! 欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞!🌟
文章目录
- 3.8 Monitor 概念
- 3.9 wait notify
- 3.10 wait notify 的正确姿势
- 3.11 Park & Unpark
- 3.12 重新理解线程状态转换
- **情况 1: `NEW --> RUNNABLE`**
- **情况 2:` RUNNABLE <--> WAITING`**
- 情况 3: `RUNNABLE <--> WAITING`
- 情况 4 `RUNNABLE <--> WAITING`
- 情况 5 `RUNNABLE <--> TIMED_WAITING`
- 情况 6 `RUNNABLE <--> TIMED_WAITING`
- 情况 7 RUNNABLE <--> TIMED_WAITING
- 情况 8 `RUNNABLE <--> TIMED_WAITING`
- 情况 9 `RUNNABLE <--> BLOCKED`
- 情况 10 `RUNNABLE <--> TERMINATED`
- 3.13 多把锁
- 3.14 活跃性
- 3.15 ReentrantLock
3.8 Monitor 概念
3.8.1 Java 对象头
以 32 位虚拟机为例
普通对象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
- klass word:指向类型元数据的指针。比如 student类型的对象,指向Student类型
数组对象
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
其中 Mark Word 结构为
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:0 | 01 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | 00 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|-------------------------------------------------------|--------------------|
- hashcode:每个对象都有一个hashcode值
- age:垃圾回收时的分代年龄
- biased_lock:是否偏向锁
- 加锁状态
- ptr_to_heavyweight_monitor:指向操作系统中monitor的指针
- ptr_to_lock_record:锁记录地址
以Integer对象为例,占 8 + 4
个字节(对象头 + value),基本类型int占 4个字节
3.8.2 原理之 Monitor(锁)
3.8.3 原理之 synchronized
3.8.4 小故事
- 故事角色
- 老王 - JVM
- 小南 - 线程
- 小女 - 线程
- 房间 - 对象
- 房间门上 - 防盗锁 - Monitor
- 房间门上 - 小南书包 - 轻量级锁
- 房间门上 - 刻上小南大名 - 偏向锁
- 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值
- 不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向
小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁(Monitor),当上下文切换时,锁住门。这样,即使他离开了,别人也进不了门,他的工作就是安全的。— 重量级锁
但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女晚上用。每次上锁太麻烦了,有没有更简单的办法呢?— 挂书包(轻量级锁)
小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因此每次进门前得翻翻书包,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是自己的,那么就在门外等,并通知对方下次用锁门的方式(存在竞争时,锁升级,轻量级 --> 重量级)。
后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍然觉得麻烦。
于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】,(偏向锁)下次来用房间时,只要名字还在,那么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦掉,升级为挂书包的方式。(存在竞争时,锁升级,偏向锁 --> 轻量级锁)
同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老王(JVM)觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字(批量重偏向)
后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包(不可偏向)
3.8.5 原理之 synchronized 进阶
3.9 wait notify
3.9.1 小故事 - 为什么需要 wait
-
由于条件不满足,小南不能继续进行计算
-
但小南如果一直占用着锁,其它人就得一直阻塞,效率太低
-
于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由老王随机安排进屋
-
直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)
-
小南于是可以离开休息室,重新进入竞争锁的队列
3.9.2 原理之 wait / notify
Owner 线程获取了锁,但是发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
WAITING线程和BLOCKED线程的区别?
- WAITING:已经获得了锁,但是条件不满足,释放锁后进入WaitSet队列;
- BLOCKED:没有获得锁,进入EntryList中等待,状态是BLOCKED
==相同点:==都处于阻塞状态,不占用CPU时间片,将来调度时候也不会考虑。
WAITING线程和BLOCKED线程的唤醒条件?
-
WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
-
BLOCKED 线程会在 Owner 线程释放锁时唤醒
3.9.3 API 介绍
方法名称 | 描述 |
---|---|
obj.wait() | 让进入 object 监视器的线程到 waitSet 等待 |
obj.notify() | 在 object 上正在 waitSet 等待的线程中挑一个唤醒 |
obj.notifyAll() | 让 object 上正在 waitSet 等待的线程全部唤醒 |
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
-
wait:先成为Owner,才有资格进入WaitSet等待。
-
Notify:必须成为Owner,才能叫醒隔壁的WaitSet中的线程。
**案例一:**当没有获得锁,就使用wait/notify/notifyAll方法时,报IllegalMonitorStateException
@Slf4j(topic = "c.Test18")
public class Test18 {
static final Object lock = new Object();
public static void main(String[] args) {
// synchronized (lock) {
try {
lock.wait();//IllegalMonitorStateException
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
}
**案例二:**演示Notify和NotifyAll区别
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t2").start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notify(); // 唤醒obj上一个线程
// obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
notify 的一种结果
15:38:17.518 c.TestWaitNotify [t1] - 执行....
15:38:17.520 c.TestWaitNotify [t2] - 执行....
15:38:19.519 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
15:38:19.519 c.TestWaitNotify [t1] - 其它代码....
notifyAll 的结果
15:37:19.908 c.TestWaitNotify [t1] - 执行....
15:37:19.910 c.TestWaitNotify [t2] - 执行....
15:37:21.908 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
15:37:21.908 c.TestWaitNotify [t2] - 其它代码....
15:37:21.908 c.TestWaitNotify [t1] - 其它代码....
3.9.4 API源码查看
wait() 和wait(long n) 的区别
- wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
- wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
public final native void notify();
public final native void notifyAll();
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException; //本地方法
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
//可以看出wait(long timeout, int nanos)的nacos参数是无用的
if (nanos > 0) {
timeout++;
}
wait(timeout);//依旧调用的是wait(long timeout)
}
3.10 wait notify 的正确姿势
3.10.0 sleep(long n) 和 wait(long n) 的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 它们状态 都是
TIMED_WAITING
(相同点) - 当不满足条件等待,最好使用wait.因为sleep不会释放锁。sleep一般是让CPU休息…:
/**
* 演示sleep和wait睡眠后,是否释放锁
*/
@Slf4j(topic = "c.Test19")
public class Test19 {
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
log.debug("获得锁");
try {
// Thread.sleep(20000);
lock.wait(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
Sleeper.sleep(1);
synchronized (lock) {
log.debug("获得锁");
}
}
}
3.10.1 问题描述
小南只有叼有烟的时候才干活,其他人只要有时间片就干活,用代码模拟这个过程
3.10.2 step 1
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
static final Object room = new Object();//建议:引用用final修饰,保证锁的对象就不可变。
static boolean hasCigarette = false; // 有没有烟
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
sleep(2);
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
sleep(1);
new Thread(() -> {
// 这里能不能加 synchronized (room)?
//synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
// }
}, "送烟的").start();
}
}
输出1:
17:17:31.927 c.TestCorrectPosture [小南] - 有烟没?[false]
17:17:31.935 c.TestCorrectPosture [小南] - 没烟,先歇会!
17:17:32.929 c.TestCorrectPosture [送烟的] - 烟到了噢!
17:17:33.940 c.TestCorrectPosture [小南] - 有烟没?[true]
17:17:33.940 c.TestCorrectPosture [小南] - 可以开始干活了
17:17:33.940 c.TestCorrectPosture [其它人] - 可以开始干活了
17:17:33.940 c.TestCorrectPosture [其它人] - 可以开始干活了
17:17:33.940 c.TestCorrectPosture [其它人] - 可以开始干活了
17:17:33.940 c.TestCorrectPosture [其它人] - 可以开始干活了
17:17:33.940 c.TestCorrectPosture [其它人] - 可以开始干活了
输出2:(当加上synchronized (room)
)
17:25:44.996 c.TestCorrectPosture [小南] - 有烟没?[false]
17:25:45.004 c.TestCorrectPosture [小南] - 没烟,先歇会!
17:25:47.007 c.TestCorrectPosture [小南] - 有烟没?[false]
17:25:47.007 c.TestCorrectPosture [送烟的] - 烟到了噢!
17:25:47.007 c.TestCorrectPosture [其它人] - 可以开始干活了
17:25:47.007 c.TestCorrectPosture [其它人] - 可以开始干活了
17:25:47.007 c.TestCorrectPosture [其它人] - 可以开始干活了
17:25:47.007 c.TestCorrectPosture [其它人] - 可以开始干活了
17:25:47.007 c.TestCorrectPosture [其它人] - 可以开始干活了
分析:
- 其它干活的线程,都要一直阻塞,效率太低
- 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
- main加了
synchronized (room)
后,就好比小南在里面反锁了门睡觉,烟根本没法送进门。没加
synchronized 就好像 main 线程是翻窗户进来的 - 解决方法,使用
wait - notify
机制
3.10.3 step 2
使用wait-notify改进上面代码
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait(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();
}
sleep(1);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
room.notify();
}
}, "送烟的").start();
}
}
输出:
17:42:24.719 c.TestCorrectPosture [小南] - 有烟没?[false]
17:42:24.727 c.TestCorrectPosture [小南] - 没烟,先歇会!
17:42:24.727 c.TestCorrectPosture [其它人] - 可以开始干活了
17:42:24.727 c.TestCorrectPosture [其它人] - 可以开始干活了
17:42:24.727 c.TestCorrectPosture [其它人] - 可以开始干活了
17:42:24.727 c.TestCorrectPosture [其它人] - 可以开始干活了
17:42:24.727 c.TestCorrectPosture [其它人] - 可以开始干活了
17:42:25.719 c.TestCorrectPosture [送烟的] - 烟到了噢!
17:42:25.719 c.TestCorrectPosture [小南] - 有烟没?[true]
17:42:25.719 c.TestCorrectPosture [小南] - 可以开始干活了
分析:
- 此改进可以让其他线程同时运行,不会占用锁,并发效率大大提升
- 思考:如果有其他线程也在等待,那么会不会错误的叫醒了其他线程?
3.10.4 step 3-4
加入另外一个线程 小女线程时,当外卖送到,小女可开始工作。思考并分析运行结果
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
// 虚假唤醒
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notify();
// room.notifyAll();
}
}, "送外卖的").start();
}
}
输出1:(使用notify
)
- notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线
程,称之为【虚假唤醒】 - 解决方法,改为 notifyAll
输出2:(使用notifyAll
)
- 用 notifyAll 仅解决某个线程的唤醒问题,但使用
if + wait
判断仅有一次机会,一旦条件不成立,就没有重新
判断的机会了(比如小南被错误唤醒后,就不能重新判断了) - 解决方法,用
while + wait
,当条件不成立,再次 wait
3.10.5 step 5
将 if
改为 while
,防止虚假唤醒
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep5 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
输出:
**结论:**此方法满足需求。即提高了并发效率问题,又不会出现虚假唤醒问题。
思考:while中使用wait()相比wait(long num)会不会浪费CPU呢?
只有线程被唤醒才会接着while,不唤醒就是wait,所以不会有cpu空转
3.10.6 使用wait和notify的正确姿势总结
synchronized(lock) {
while(条件不成立) {//while防止虚假唤醒
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();//notifyAll唤醒所有等待序列中国的线程
}
3.11 Park & Unpark
3.11.1 基本使用
它们是 LockSupport 类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
先 park 再 unpark
@Slf4j(topic = "c.TestParkUnpark")
public class TestParkUnpark {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("start...");
Sleeper.sleep(1);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
Sleeper.sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);
}
}
先 unpark 再 park:交换main和t1线程sleep的时间
3.11.2 特点
与 Object 的 wait & notify 相比
- wait,notify 和 notifyAll 必须配合 Object Monitor(锁) 一起使用,而 park,unpark 不必
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify (先notify的代码会被忽略)
3.11.3 原理之 park & unpark
3.12 重新理解线程状态转换
- NEW:初始状态;创建了Java线程对象,还没有与操作系统的线程相关联。
情况 1: NEW --> RUNNABLE
- 当调用
t.start()
方法时,由NEW --> RUNNABLE
情况 2: RUNNABLE <--> WAITING
t 线程用 synchronized(obj)
获取了对象锁后
- 调用
obj.wait()
方法时,t 线程从RUNNABLE --> WAITING
- 调用
obj.notify()
,obj.notifyAll()
,t.interrupt()
时- 竞争锁成功,t 线程从
WAITTING --> RUNNABLE
- 竞争锁失败,t 线程依旧是
WAITTING --> BLOCKED
- 竞争锁成功,t 线程从
**注意:**第二步中,调用 obj.notify()
, obj.notifyAll()
, t.interrupt()
但未释放锁,t 线程会先进入EntryList 等待竞争锁,此时为BLOCKED
状态。持锁线程释放锁后 EntryList中的线程会进行竞争。然后 根据竞争结果,t 线程会处于不同的状态。此过程可在后续Debug分析中清晰看到…
测试代码
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t2").start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程
obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
Debug分析:
情况 3: RUNNABLE <--> WAITING
- 当前线程调用
t.join()
方法时,当前线程从RUNNABLE --> WAITING
- 注意是当前线程在t 线程对象的监视器上等待
- t 线程运行结束,或调用了当前线程的
interrupt()
时,当前线程从WAITING --> RUNNABLE
情况 4 RUNNABLE <--> WAITING
- 当前线程调用
LockSupport.park()
方法会让当前线程从RUNNABLE --> WAITING
- 调用
LockSupport.unpark(目标线程)
或调用了线程 的interrupt()
,会让目标线程从WAITING --> RUNNABLE
情况 5 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
- 竞争锁成功,t 线程从
情况 6 RUNNABLE <--> TIMED_WAITING
- 当前线程调用
t.join(long n)
方法时,当前线程从RUNNABLE --> TIMED_WAITING
- 注意是当前线程在t 线程对象的监视器上等待
- 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的
interrupt()
时,当前线程从
TIMED_WAITING --> RUNNABLE
情况 7 RUNNABLE <–> TIMED_WAITING
- 当前线程调用
Thread.sleep(long n)
,当前线程从RUNNABLE --> TIMED_WAITING
- 当前线程等待时间超过了 n 毫秒,当前线程从
TIMED_WAITING --> RUNNABLE
情况 8 RUNNABLE <--> TIMED_WAITING
- 当前线程调用
LockSupport.parkNanos(long nanos)
或LockSupport.parkUntil(long millis)
时,当前线
程从RUNNABLE --> TIMED_WAITING
- 调用
LockSupport.unpark(目标线程)
或调用了线程 的interrupt()
,或是等待超时,会让目标线程从
TIMED_WAITING--> RUNNABLE
情况 9 RUNNABLE <--> BLOCKED
- t 线程用
synchronized(obj)
获取了对象锁时如果竞争失败,从RUNNABLE --> BLOCKED
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有
BLOCKED
的线程重新竞争,如果其中 t 线程竞争
成功,从BLOCKED --> RUNNABLE
,其它失败的线程仍然BLOCKED
情况 10 RUNNABLE <--> TERMINATED
当前线程所有代码运行完毕,进入TERMINATED
3.13 多把锁
多把不相干的锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)
例如
public class TestMultiLock {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
}
}
@Slf4j(topic = "c.BigRoom")
class BigRoom {
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (this) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
改进:
@Slf4j(topic = "c.BigRoom")
class BigRoom {
//使用多把锁,相当于把房间分成了两块,允许两个没有关联的动作同时进行
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
将锁的粒度细分
- 好处,是可以增强并发度
- 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
3.14 活跃性
3.14.1 死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程
获得A对象
锁,接下来想获取 B对象
的锁 。t2 线程
获得 B对象
锁,接下来想获取 A对象
的锁。
代码演示:
@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
public static void main(String[] args) {
test1();
}
private static void test1() {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
结果:
15:13:19.339 c.TestDeadLock [t2] - lock B
15:13:19.339 c.TestDeadLock [t1] - lock A
3.14.2 定位死锁
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:
- 使用jps+jstack定位死锁
F:\自学课程\底层课程\Java并发编程\concurrent>jps //查看正在运行的Java进程
11188 Launcher
17828 Jps
15192 TestDeadLock
19404
4684 RemoteMavenServer36
F:\自学课程\底层课程\Java并发编程\concurrent>jstack 15192//查看进程的详细信息
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.192-b12 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002e13800 nid=0x428c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
//以下是t2线程的详细信息
"t2" #12 prio=5 os_prio=0 tid=0x000000001ed35800 nid=0x290 waiting for monitor entry [0x000000001f37f000]
java.lang.Thread.State: BLOCKED (on object monitor)//阻塞状态
at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$1(TestDeadLock.java:32)
- waiting to lock <0x000000076ec5d160> (a java.lang.Object)//正在等待的锁...
- locked <0x000000076ec5d170> (a java.lang.Object)//已经获得的锁
at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$2/1792845110.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
//以下是t1线程的详细信息
"t1" #11 prio=5 os_prio=0 tid=0x000000001ed35000 nid=0xcd4 waiting for monitor entry [0x000000001f27f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$0(TestDeadLock.java:21)
- waiting to lock <0x000000076ec5d170> (a java.lang.Object)
- locked <0x000000076ec5d160> (a java.lang.Object)
at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$1/897913732.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
//省略部分输出
Found one Java-level deadlock: //发现一个Java级别的死锁
=============================
"t2":
waiting to lock monitor 0x0000000002f0c0c8 (object 0x000000076ec5d160, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x0000000002f0b9e8 (object 0x000000076ec5d170, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2"://出现死锁的行号信息
at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$1(TestDeadLock.java:32)
- waiting to lock <0x000000076ec5d160> (a java.lang.Object)
- locked <0x000000076ec5d170> (a java.lang.Object)
at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$2/1792845110.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"t1":
at cn.itcast.n4.deadlock.TestDeadLock.lambda$test1$0(TestDeadLock.java:21)
- waiting to lock <0x000000076ec5d170> (a java.lang.Object)
- locked <0x000000076ec5d160> (a java.lang.Object)
at cn.itcast.n4.deadlock.TestDeadLock$$Lambda$1/897913732.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
- 使用Jconsole工具
- 避免死锁要注意加锁顺序
- 另外如果由于某个线程进入了死循环,导致其它线程一直等待。对于这种情况 linux 下可以通过 jps+jstack方式进行排查。
3.14.3 哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 如果筷子被身边的人拿着,自己就得等待
//就餐测试类
public class TestDeadLock {
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("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c1, c5).start();
}
}
//哲学家类
@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;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
synchronized (left) {
// 尝试获得右手筷子
synchronized (right) {
eat();
}
}
}
}
Random random = new Random();
private void eat() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
//筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
执行不多会,就执行不下去了
12:33:15.575 [苏格拉底] c.Philosopher - eating...
12:33:15.575 [亚里士多德] c.Philosopher - eating...
12:33:16.580 [阿基米德] c.Philosopher - eating...
12:33:17.580 [阿基米德] c.Philosopher - eating...
// 卡在这里, 不向下运行
使用 jconsole 检测死锁,发现
-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)
这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况
3.14.4 活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
**解决办法:**让两个线程的执行时间交错,具体方案是通过增加随机睡眠时间。
3.14.5 饥饿
很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题。
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题
顺序加锁的解决方案(线程1,2获取锁的顺序是相同的)
案例演示:
3.15 ReentrantLock
相对于 synchronized
它具备如下特点
- 可中断(使用其他线程或者方法取消锁)
- 可以设置超时时间(一段时间内未获取到锁,放弃去争抢锁,执行一些其他逻辑操作)
- 可以设置为公平锁(先进先出,防止出现锁饥饿现象)
- 支持多个条件变量(允许有多个WaitSet,不满足条件1时,去waitSet1中等待,不满足2时,去waitSet2中等待。当然唤醒时,也可以根据条件进行唤醒)
这里的中断是指,别的线程可以破坏你的blocking状态,而不是指自己中断阻塞状态
**相同点:**与 synchronized 一样,都支持可重入(同一线程对对象可以重复加锁)
基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
3.15.1 可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
@Slf4j(topic = "c.Test17")
public class Test17 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
}
输出:
15:33:20.791 c.Test17 [main] - execute method1
15:33:20.794 c.Test17 [main] - execute method2
15:33:20.794 c.Test17 [main] - execute method3
3.15.2 可打断
/**
* @author lxy
* @version 1.0
* @Description ReentrantLock可打断特性示例
* @date 2022/7/3 17:29
* 使用lock.lockInterruptibly()的优点:可以防止死锁的产生,避免长时间的等待。
*/
@Slf4j(topic = "c.Test18")
public class Test18 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
//如果没有竞争那么此方法就会获取lock对象锁
//如果有竞争就进入阻塞队列,可以被其他线程用 interrupt 方法
log.debug("尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("没有获得锁,返回");
return;
}
try {
log.debug("获取到锁");
} finally {//为啥俩try不能被合并:因为被打断的话,不能unlock,只有获取锁了才能继续往下走,往下走又必须来一个finally来保证锁一定被释放
lock.unlock();//释放锁
}
}, "t1");
//1.当只有一个t1线程时
//t1.start();
//2.当有t1和main线程时
// lock.lock();
// t1.start();
//3.当t1被interrupt打断时
// lock.lock();
// t1.start();
// Sleeper.sleep(2);
// log.debug("打断t1");
// t1.interrupt();
}
}
输出:
- 当只有一个t1线程时
17:55:18.341 c.Test18 [t1] - 尝试获取锁
17:55:18.343 c.Test18 [t1] - 获取到锁
- 当有t1和main线程时
- 当t1被interrupt打断时
- 如果将上锁代码替换成
lock.lock();
==可打断锁的意义:==可以防止死锁的产生,避免长时间的等待。
3.15.3 锁超时介绍以及应用
可打断和锁超时的区别:可打断是线程t1调用interrupt方法进行打断阻塞状态,是被动的;而锁超时是超过一定时间就放弃获得,是主动的。
- 使用方式一:
tryLock
@Slf4j(topic = "c.Test19")
public class Test19 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
if (!lock.tryLock()) {
log.debug("获取不到锁");
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
}
}
输出:
19:27:15.757 c.Test19 [main] - 获得到锁
19:27:15.759 c.Test19 [t1] - 尝试获得锁
19:27:15.759 c.Test19 [t1] - 获取不到锁
-
使用方式2:使用
trylock(long timeout,TimeUnit unit)
且超时
输出:超时后放弃获取锁
- 使用方式三:使用
trylock(long timeout,TimeUnit unit)
且未超时
@Slf4j(topic = "c.Test19")
public class Test19 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
if(!lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
}catch (InterruptedException e){
e.printStackTrace();
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
Sleeper.sleep(1);
lock.unlock();
log.debug("释放了锁");
}
}
输出:
锁超时的应用-解决哲学家就餐问题
//测试死锁
public class TestDeadLock {
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("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
//哲学家类
@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;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if(left.tryLock()){
try {
// 尝试获得右手筷子
if(right.tryLock()){
try {
eat();
}finally {//为了保证获取右手锁后可以成功释放,所以需要加一个 try...catch块
right.unlock();
}
}
}finally {//为了保证获取左手锁后可以成功释放,所以需要加一个try...catch
left.unlock();
}
}
}
}
Random random = new Random();
private void eat() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
//筷子类
class Chopstick extends ReentrantLock {//让筷子锁对象有重入锁的一些特征
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
输出:
14:20:25.184 c.Philosopher [亚里士多德] - eating...
14:20:25.199 c.Philosopher [苏格拉底] - eating...
14:20:25.705 c.Philosopher [柏拉图] - eating...
14:20:25.705 c.Philosopher [赫拉克利特] - eating...
14:20:26.205 c.Philosopher [苏格拉底] - eating...//哲学家们可以正常的进餐
3.15.4 公平锁
ReentrantLock 默认是不公平的,但是可以通过构造方法来设置是否是公平锁。
公平的举例:
- 不公平的锁,比如
synchronized
;每次当有线程A占用锁对象,其他线程会进入阻塞队列进行等待,当A使用完后,释放锁,其他线程就会进行竞争锁,抢到了就可以使用。所以这个过程时不公平的。 - 公平的锁,比如
ReentrantLock
,按照线程进入阻塞队列的顺序来获得锁,先来先得。
公平锁主要来解决线程饥饿问题。前面我们使用tryLock()
也可以进行解决。所以公平锁一般没有必要,会降低并发度,后面分析原理时会讲解
**注:**关于公平锁的示例代码,这里不再演示。后面源码剖析时详细介绍。
3.15.5 条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的(休息室),这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
- await 前需要获得锁(同
synchronized
) - await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
演示代码
@Slf4j(topic = "c.Test20")
public class Test20 {
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
//等待烟的休息室
static Condition waitCigaretteSet = ROOM.newCondition();
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(()->{
ROOM.lock();
try {
log.debug("烟送到没?[{}]",hasCigarette);
while (!hasCigarette){
log.debug("没烟,先歇会!");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
}finally {
ROOM.unlock();
}
},"小南").start();
new Thread(()->{
ROOM.lock();
try {
log.debug("外卖送到没?[{}]",hasTakeout);
while (!hasTakeout){
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
}finally {
ROOM.unlock();
}
},"小女").start();
Sleeper.sleep(1);
new Thread(()->{
ROOM.lock();
try {
hasTakeout = true;
log.debug("外卖送到了");
waitTakeoutSet.signal();
}finally {
ROOM.unlock();
}
},"送外卖的").start();
Sleeper.sleep(1);
new Thread(()->{
ROOM.lock();
try {
hasCigarette = true;
log.debug("烟送到了!");
waitCigaretteSet.signal();
}finally {
ROOM.unlock();
}
},"送烟的").start();
}
}
输出:
3.15.5 同步模式之顺序控制
本章小结
本章我们需要重点掌握的是
- 分析多线程访问共享资源时,哪些代码片段属于临界区
- 使用 synchronized 互斥解决临界区的线程安全问题
- 掌握 synchronized 锁对象语法
- 掌握 synchronzied 加载成员方法和静态方法语法
- 掌握 wait/notify 同步方法
- 使用 lock 互斥解决临界区的线程安全问题
- 掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
- 学会分析变量的线程安全性、掌握常见线程安全类的使用
- 了解线程活跃性问题:死锁、活锁、饥饿
- 应用方面
- 互斥:使用 synchronized 或 Lock 达到共享资源互斥效果
- 同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果
- 原理方面
- monitor(管程)、synchronized 、wait/notify 原理
- synchronized 进阶原理
- park & unpark 原理
- 模式方面
- 同步模式之保护性暂停
- 异步模式之生产者消费者
- 同步模式之顺序控制
注意:
-
synchronized的互斥是为了临界区的代码不上下文切换,产生指令交错,从而保证临界区代码的原子性。synchronized的同步是为了解决当条件不满足时线程等待,条件满足时继续运行。ReentrantLock也可以实现互斥和同步。
-
monitor的源码是用C++写的,Java也实现了Monitor锁,即ReentrantLock