synchronized原理
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args){
synchronized(lock){
counter++;
}
}
对应的字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock引用 (synchronized开始)
3: dup
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
6: getstatic #3 // <- i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock引用
15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
16: goto 24
19: astore_2 // e -> slot 2
20: aload_1 // <- lock引用
21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
22: aload_2 // <- slot 2 (e)
23: athrow // throw e
24: return
Exception table:
from to target type
6 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
synchronized原理进阶
轻量级锁
使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化
轻量级锁对使用者是透明的,语法仍是synchronized,会优先使用轻量级锁加锁,如果加锁失败才会用重量级锁
- 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word
- 让锁记录中Object reference指向锁对象,并尝试用CAS替换 Object 的 Mark Word,将 Mark Word 的值存 入锁记录
- 如果CAS替换成功,对象头中存储了锁记录地址和状态 00,表示由该线程给对象加锁,这时图示如下
-
如果CAS失败
-
如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀
-
如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数
-
-
当退出synchronized代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
-
当退出synchronized代码块时,锁记录的值不为null,这时使用CAS将MarkWord的值恢复给对象头
- 成功,代表解锁成功
- 失败,说明轻量级锁进行锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
锁膨胀
如果尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁
-
当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁
-
这时Thread-1加轻量级锁失败,进入锁膨胀
- 即为Object对象申请Monitor锁,让Object指向重量级锁地址
- 然后自己进入Monitor的EntryList阻塞 BLOCKED
-
当Thread-0退出同步代码块解锁时,使用CAS将MarkWord的值恢复给对象头,失败。这时会进入重量级锁解锁流程,即按照Monitor地址找到monitor地址找到Monitor对象,将Owner设置为null,唤醒EntryList中的BLOCKED线程
自旋优化
重量级锁竞争的时候,可以通过自旋重试来进行优化,可以一定程度上避免阻塞
- 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势
- 在java6之后自旋锁是自适应的,比较智能
- java7之后不能控制是否开启自旋