4 共享模型之管程
注:本系列仅供个人学习使用,原视频:https://www.bilibili.com/video/BV16J411h7Rd?p=49
4.1 共享带来的问题
问题
上下文切换与指令交错导致共享资源访问的读写问题。
临界区 Critical Section
一段代码如果存在对共享资源的多线程读写操作,称这段代码为临界区。
竞争条件Race Condition
多个线程在临界区执行,由于代码的执行序列不同而导致结果无法预测,称之为繁盛了静态条件。
4.2 synchronized解决方案
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
synchronized(对象) // 线程1, 线程2(blocked)
{
临界区
}
面向对象的改进
class Room {
int value = 0;
public void increment() {
synchronized (this) {
value++;
}
}
public void decrement() {
synchronized (this) {
value--;
}
}
public int get() {
synchronized (this) {
return value;
}
}
}
线程八锁
主要判断锁的对象是否相同(当前对象、类对象)
4.3 方法上添加synchronized
对象锁与方法锁相比,对象锁的效率更高。synchronized也可以加载方法上,此时默认锁的是this当前对象。synchronized俗称对象锁。
4.4 变量的线程安全分析
成员变量和静态变量
- 如果没有共享则线程安全
- 如果线程共享了
- 都是读操作,线程安全(待定)
- 有读写,线程不安全
局部变量是否安全
- 局部变量是安全的(每个线程都有自己专属的栈区)
- 局部变量引用未必
- 局部引用没有逃离方法去,线程安全
- 局部引用逃离方法区,线程不安全
常见的线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent包下的类
这里的线程安全是指,多个线程调用它同一个实例的某个方法时是线程安全的。
Hashtable table = new Hashtable();
new Thread(()->{
table.put("key", "value1");
}).start();
new Thread(()->{
table.put("key", "value2");
}).start();
- 它们的每个方法是原子的
- 但注意它们多个方法的组合不是原子的
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
table.put("key", value);
}
不可变类型线程安全
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。
4.5 习题
public class ExerciseSell {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
log.debug("selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
log.debug("remainder count:{}", ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
另外,用下面的代码行不行,为什么?
List<Integer> sellCount = new ArrayList<>();
测试脚本
for /L %n in (1,1,10) do java -cp ".;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackclassic\1.2.3\logback-classic-1.2.3.jar;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackcore\1.2.3\logback-core-1.2.3.jar;C:\Users\manyh\.m2\repository\org\slf4j\slf4japi\1.7.25\slf4j-api-1.7.25.jar" cn.itcast.n4.exercise.ExerciseSell
转账练习
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看转账2000次后的总金额
log.debug("total:{}",(a.getMoney() + b.getMoney()));
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~100
public static int randomAmount() {
return random.nextInt(100) +1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
这样改正行不行,为什么?
public synchronized void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
4.6 Monitor概念
Java对象头
以32位虚拟机为例
普通对象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
数组对象(多了32位数组长度)
|---------------------------------------------------------------------------------|
| 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 |
|-------------------------------------------------------|--------------------|
64位虚拟机Mark Word
|--------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal |
|--------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
|--------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked |
|--------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|--------------------------------------------------------------------|--------------------|
参考资料:https://stackoverflow.com/questions/26357186/what-is-in-java-object-header
Monitor(锁)
Monitor被反以为监视器或管程
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置为指向Monitor对象的指针。Monitor结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdt1rAqA-1592232545022)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527110414279.png)]
- obj没有被锁的情况,当Thread-1执行synchronized(obj)时,JVM会将obj关联到操作系统的一个Monitor,并在对象头中的Mark Word里面记录Monitor的指针。此时Monitor会将Owner指向该对象所在线程。
- 如果此时有另一个线程Thread-2执行synchronized(obj),由于阻塞,Monitor会将新的线程添加到EntryList中。
- 当Thread-1执行完毕之后,Monitor会将Owner置null,并唤醒EntryList,选择一个线程作为Owner所指。
注意:
- synchronized必须室进入同一对象的monitor才有上述效果
- 不加synchronized的对象不会关联监视器,不遵从上述规则
*原理之synchronized
public class Synchronized1 {
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized(lock){
counter++;
}
}
}
查看字节码
javap -c Synchronized1.class
输出:main函数对应的字节码
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field lock:Ljava/lang/Object; lock引用
3: dup // 复制引用
4: astore_1 // lock引用暂存到 slot 1
5: monitorenter // 在lock对象的MarkWord中写入Monitor的指针
6: getstatic #13 // Field counter:I,获取静态变量counter
9: iconst_1 //准备常量1
10: iadd // +1
11: putstatic #13 // Field counter:I,将结果保存在counter里面
14: aload_1 // 获取lock对象引用
15: monitorexit // 将lock对象MarkWord重置,唤醒EntryList
16: goto 24 // 异常处理(异常发生时,将锁释放)
19: astore_2 // 将捕获的异常进行暂存
20: aload_1 // 加载lock引用
21: monitorexit // 对lock对象的MarkWord进行还原
22: aload_2 // 加载暂存的异常
23: athrow // 抛出异常
24: return // 返回
Exception table: // 异常监视表,from...to...表示监视的代码行数
from to target type
6 16 19 any
19 22 19 any
*原理之synchronized进阶
轻量级锁
一个对象虽然有多线程访问,但是多线程访问时间是错开的。也就是每次都会分配Monitor对象,这样做会消耗系统资源。此时可以使用轻量级锁来优化。
轻量级锁对使用者来说是透明的,即语法依然是synchronized
。轻量锁操作过程如下。
假设有两个方法同步同一个对象,利用同一个对象锁。
static final Object obj = new Object();
public static void method1(){
synchronized(obj){
// 同步块 A
method2()
}
}
public static void method2(){
synchronized(obj){
// 同步块 B
}
}
-
每个线程都有独立地栈帧当方法一对obj加锁时,会在线程栈中创建一个锁记录对象(Lock Record)。内部可以存储锁定对象的Mark Word与引用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yp8euJOK-1592232545028)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114301832.png)]
-
如果cas(compare-and-swap)成功,对象头中存储了
锁记录地址和状态00
,图示如下:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0d4poeC-1592232545031)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114454740.png)]
1)锁重入
cas失败情况1:同个线程内有多个栈帧对同一对象加锁
这种情况是锁重入,那么在添加一条Lock Record作为重入的计数。
此时没有发生Mark Word替换,主要由以下原因:对象中已经存储了对一个对其加轻量锁的Lock Record对象,交换会发生覆盖,导致错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HR7WbGxz-1592232545034)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114927882.png)]
- 当退出synchronized代码块时,锁记录值不为null,则使用cas将Mark WOrd恢复给对象。
- 成功,则解锁成功。
- 失败说明轻量锁进行了锁膨胀,或者已经升级为重量锁,进入重量锁解锁过程。
2)锁膨胀
cas失败情况2:另外一个线程对同一个对象加锁。如果在尝试加轻量级锁的过程中,CAS操作无法成功,这一情况为其他线程为此对象加上了轻量级锁,这是需要进行锁膨胀,将轻量级锁升级为重量级锁。
- 当Thread-1尝试给对象加锁时,发现Thread-0已经加过轻量级锁了。这时进入锁膨胀流程:
- 即为obj对象添加Monitor锁。
- 首先申请Monitor锁。
- 然后更改对象的Mark Word。将其指向Monitor
- 最后将自己添加到Monitor的EntryList中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfFpCxgy-1592232545037)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527141250203.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HarUwCrk-1592232545038)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527141702823.png)]
- 当Thread-0退出同步块解锁时,使用cas将Mark Word回复给对象头,失败。这时会进入重量级解锁流程。即按照Monitor地址找到Monitor对象,将Owner设置为null,最后唤醒EntryList中的BLOCKED线程。
3)自旋优化
重量级锁竞争的时候,还可以使用自旋来进行优化,如果自旋成功(把持锁的线程已经退出了同步,释放了锁),这时,当前线程就可以避免阻塞。
具体做法类似于设置while true循环,访问若干次。
- 自旋锁会占用CPU时间,单CPU就是浪费,多核自旋才能发挥优势。
- Java 6 之后自旋锁是自适应的,如果第一次自旋成功,那么会认为自旋成功的可能性比较高,否则就少自旋,甚至不自旋
偏向锁
轻量级锁在没有竞争的时候,每次重入仍需执行CAS操作。
Java6 中引入了偏向锁来做进一步优化:只有第一次使用CAS江县城ID设置到对象的Mark Word头,之后发现这个线程ID就是自己的,就表示没有竞争,不用CAS。已有只要不发生竞争,这个对象酒柜线程所拥有。
例如,下列程序仅使用单个线程。
static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步块 C
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCqTPXiP-1592232545040)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527143514218.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5rpUQJA-1592232545042)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527143525604.png)]
偏向状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTiEMARp-1592232545044)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527144349939.png)]
|--------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal |
|--------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
|--------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked |
|--------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|--------------------------------------------------------------------|--------------------|
测试关闭偏向锁
- 添加VM参数(本身程序就应用于高并发的场景)
-XX:-UseBiasedLocking
-
调用hashcode(),可以屏蔽掉偏向锁。
- 轻量级锁会在锁记录中记录 hashCode
- 重量级锁会在 Monitor 中记录 hashCode
-
有多个线程适用对象
private static void test2() throws InterruptedException { Dog d = new Dog(); Thread t1 = new Thread(() -> { synchronized (d) { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); } synchronized (TestBiased.class) { TestBiased.class.notify(); } // 如果不用 wait/notify 使用 join 必须打开下面的注释 // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的 /*try { System.in.read(); } catch (IOException e) { e.printStackTrace(); }*/ }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (TestBiased.class) { try { TestBiased.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); } log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); }, "t2"); t2.start(); }
第二个线程加锁过程中会升级成轻量级锁,释放后变为未偏向的状态。
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 [t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 [t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 [t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
-
使用wait/notify
- 因为wait/notify重量级锁才有
public static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重偏向
虽然对象被多个线程访问,但是没有竞争,这是偏向线程T1的对象仍有机会偏向T2,重偏向会重置对象的Thread ID。
当撤销偏向锁阈值超过20次时,jvm会判定偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程。
例子:对一个list内的对象进行加偏向锁,从第20次开始,后续的对象被被重新加了Thread-2的偏向锁。
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
撤销偏向
当撤销偏向锁超过40次后,jvm会觉得偏向错了,不应该偏向,于是整个类的对象都会变成不可偏向的,新建的对象也是不可偏向的。
package test;
import java.util.Vector;
import java.util.concurrent.locks.LockSupport;
public class BiasBatchCancel {
static Thread t1, t2, t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
}
锁消除
Java运行时有JIT即时编译器,会对热点代码进行优化。比如当没有多线程时,会将synchronized优化掉
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
java -jar benchmarks.jar
锁优化,运行速度相近
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op
java -XX:-EliminateLocks -jar benchmarks.jar
去除锁优化,运行变慢
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
锁粗化
4.7 wait notify
API介绍
obj.wait()
让object监视器的线程到waitSet等待wait(long n)
有时限的等待, 到 n 毫秒后结束等待,或是被 notifyobj.notify()
从obj关联的monitor中的waitSet随机挑选一个线程将其唤醒obj.notifyAll()
唤醒obj关联的monitor中的waitSet中的所有线程
wait/notify与Thread.sleep的区别
- sleep属于Thread的静态方法,而wait是Object的方法
- sleep不需要强制和synchronized配合使用,但wait需要
- sleep在睡眠时不会释放对象锁,但在wait时会释放
sleep与wait的状态都是TIMED_WAITING
正确使用的姿势
synchronized(lock){
while(条件不成立){
lock.wait();
}
//干活
}
// 另一个线程
synchronized(lock){
lock.notifyAll();
}
4.9 park与unpark
java.util.concurrent.locks.LockSupport
LockSupport 提供的是一个许可,如果存在许可,线程在调用park
的时候,会立马返回,此时许可也会被消费掉,如果没有许可,则会阻塞。调用unpark的时候,如果许可本身不可用,则会使得许可可用。许可只有一个不可累加。park的许可通过原子变量_count实现,当被消耗时,_count为0,只要拥有许可,就会立即返回。
每个线程都有自己的一个 Parker 对象。
基本使用1:
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(1);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);
基本使用2:unpark可以在park之前调用
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(2);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);
与wait…notify的区别
- wait, notify, notifyAll必须配合Object Monitor一起使用,而park与unpark却不必。
- park与unpark以线程为单位进行阻塞和唤醒,唤醒时比notify与notifyAll要精确。
- 可以在park前调用unpark,而不能在wait前线调用notify
*原理
应用场景
park与unpark适用于producer快而consumer慢的场景。
有一点比较难理解的,是unpark操作可以再park操作之前。也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。
4.10 重新理解线程状态转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzHmFYUS-1592232545047)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200615152854139.png)]
情况 1 New–>RUNNABLE
当调用t.start()方法时发生
情况 2 RUNNABLE<–>WAITING
t线程调用synchronized(obj)时获取对象锁后
- 调用obj.wait(),t线程从RUNNABLE-->WAITING
- 调用
obj.notify()
,obj.notifyAll()
, t.interrupt() 时:- 竞争锁成功,t 线程从 WAITING --> RUNNABLE
- 竞争锁失败,t 线程从 WAITING --> BLOCKED
情况 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
情况 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
情况 10 RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
4.11多个不相干锁
将锁粒度细分:
- 增强并发度
- 容易发生死锁
4.12 活跃性
死锁
多个线程需要获得多个锁(有竞争关系),容易造成死锁
死锁定位
- jps找到JVM进程 id,然后使用jstack定位死锁
- jconsole
哲学家就餐
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 如果筷子被身边的人拿着,自己就得等待
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
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();
}
}
饥饿
一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束。
4.13 ReentrantLock
相较于synchronized,ReetrantanceLock具有以下特点
- 可中断
- 可以设置超时等待
- 可以设置为公平锁
- 支持多个条件变量
基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。
可打断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 如果没有竞争,那么机会获取Lock对象
// 如果有竞争就会进入阻塞队列,可以被其他线程用interrupt打断
lock.lockInterruptibly();
log.debug("t1获得到了锁");
}catch (InterruptedException e){
e.printStackTrace();
log.debug("在等待过程中被打断");
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
Thread.sleep(1);
t1.interrupt();
log.debug("执行打断");
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
log.debug("释放了锁");
}
锁超时TryLock
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
// if (!lock.tryLock()) {
// log.debug("获取立刻失败,返回");
// // 没有获得锁,立即返回
// return;
// }
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();
try {
Thread.sleep(2);
// t1可以被打断
// t1.interrupt();
} finally {
lock.unlock();
}
公平锁
ReentranLock默认是不公平的,可以通过调用重载方法构造公平锁。
ReentrantLock lock = new ReentrantLock(false);
公平锁一般没有必要,会降低并发度
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待。
ReentrantLock条件变量的强大之处在于,他可以设置多个条件变量
- synchronized将所有不满足条件的线程都放在一个队列里面
- 而ReentrantLock支持多条件变量,可以设置更细粒度的等待队列
使用要点:
- await需要获得锁
- await执行后会释放
- await的线程被唤醒后(打断、超时)会重新竞争Lock锁
- 竞争Lock锁成功后,从await处继续执行
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitbreakfastQueue.signal();
* 同步之条件变量
三种方法实现顺序输出
wait notify
public class Test27_Control_Thread_Order_WAN {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
Thread t1 = new Thread(() -> {
wn.print("a", 1, 2);
});
Thread t2 = new Thread(() -> {
wn.print("b", 2, 3);
});
Thread t3 = new Thread(() -> {
wn.print("c", 3, 1);
});
t1.start();
t2.start();
t3.start();
}
}
/*
输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1
*/
class WaitNotify{
// 等待标记
private int flag;
private int loopNumber;
public void print(String str, int waitFlag, int nextFlag){
for(int i = 0; i < loopNumber; i++){
synchronized (this){
while(flag != waitFlag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(str);
flag = nextFlag;
this.notifyAll();
}
}
}
WaitNotify(int f, int ln){
flag = f;
loopNumber = ln;
}
}
ReentrantLock
public class Test27_Control_Thread_Order_ReentrantLock2 {
public static void main(String[] args) {
AwaitSignal as = new AwaitSignal(5);
Condition c1 = as.newCondition();
Condition c2 = as.newCondition();
Condition c3 = as.newCondition();
new Thread(() -> {
as.print("a", c1, c2);
}).start();
new Thread(() -> {
as.print("b", c2, c3);
}).start();
new Thread(() -> {
as.print("c", c3, c1);
}).start();
as.start(c1);
}
}
@Slf4j
class AwaitSignal extends ReentrantLock{
private int loopNumber;
public AwaitSignal(int loopNumber){
this.loopNumber = loopNumber;
}
public void print(String msg, Condition current, Condition next){
for(int i = 0; i < loopNumber; i++){
this.lock();
try {
current.await();
System.out.print(msg);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
unlock();
}
}
}
public void start(Condition d){
this.lock();
try {
log.debug("start");
d.signal();
}finally {
this.unlock();
}
}
}
park unpark
public class Test27_Control_Thread_Order_Park {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print("a", t2);
});
t2 = new Thread(() -> {
pu.print("b", t3);
});
t3 = new Thread(() -> {
pu.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark{
private int loopNumber;
public ParkUnpark(int loopNumber){
this.loopNumber = loopNumber;
}
public void print(String msg, Thread next){
for(int i = 0; i < loopNumber; i++){
LockSupport.park();
System.out.println(msg);
LockSupport.unpark(next);
}
}
}
4.12 本章小结
- 多线程访问共享资源时,哪些代码片段是属于临界区
- 使用sychronized互斥解决临界区线程安全问题
- 掌握synchronized对象锁语法
- 掌握synchronized加载成员方法和静态方法语法
- 掌握wait/notify同步方法
- 使用ReentrantLock互斥解决临界区线程安全问题
- 掌握使用细节:可打断、锁超时、公平锁、条件变量
- 学会分析变量的线程安全性,掌握常用的线程安全类
- 了解县城活跃性问题:死锁、活锁、饥饿
- 应用
- 互斥:使用synchronized或ReentrantLock达到动向资源的互斥效果
- 同步:使用wait/notify或ReentrantLock的条件变量来达到线程间的通信效果
- 原理方面
- monitor、synchronized、wait/notify原理
- synchronized进阶原理(轻量级、重量级Monitor、锁消除、自旋优化)
- park&unpark原理
- 模式方面
- 同步模式之保护暂停
- 异步模式之生产者消费者
- 同步模式之顺序控制