synchronized
锁定某个对象,才能访问某段代码
public class SynchronizedTest {
static Object lock = new Object();
static List<Integer> list = new ArrayList<>(10);
public static void main(String[] args) {
synchronized (lock) {
list.add(1);
}
}
}
字节码实现(编译层面):通过monitorenter 和 monitorexit 实现互斥
0 getstatic #2 <com/devin/jmm/SynchronizedTest.lock : Ljava/lang/Object;>
3 dup
4 astore_1
5 monitorenter
6 getstatic #3 <com/devin/jmm/SynchronizedTest.list : Ljava/util/List;>
9 iconst_1
10 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
13 invokeinterface #5 <java/util/List.add : (Ljava/lang/Object;)Z> count 2
18 pop
19 aload_1
20 monitorexit
21 goto 29 (+8)
24 astore_2
25 aload_1
26 monitorexit
27 aload_2
28 athrow
29 return
为什么存在两个monitorexit
同步代码块中发生异常时,synchronized自动解锁
JVM层级(Hotspot)
在对象上加锁,锁定的是对象
锁升级的过程
JDK较早版本:使用OS的资源互斥量实现,经历“用户态” -> “内核态”,属于重量级操作,效率比较低
现代版本进行了优化
无锁 -> 偏向锁 -> 轻量级锁(自旋锁)-> 重量级锁
无锁 -> 偏向锁
偏向锁 - markword 上记录当前线程指针(线程id),下次同一个线程加锁的时候,不需要竞争,只需要判断线程指针是否同一个,所以偏向锁偏向加锁的第一个线程,hashCode备份在线程栈上,线程销毁时,锁降级为无锁
偏向锁被争用 - 升级为轻量级锁
每个线程存放LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程,哪个线程就获得了轻量级锁
轻量级锁自旋次数超过10次,升级为重量级锁
如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入对象的等待队列(不消耗CPU)
重量级锁的实现,生成一个C++层面的monitor对象,markword指向这个monitor对象(互斥量),monitor提供一个阻塞队列,竞争锁的线程入队等待,由CPU调度决定获取重量级锁的线程
偏向锁由于有锁撤销的过程revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高,还不如直接使用轻量级锁(-XX:-useBiasedLocking)
synchronized VS Lock(CAS)
在高争用,高耗时的环境下synchronized效率更高
在低争用,低耗时的环境下CAS效率更高
synchronized到重量级之后是等待队列(不消耗CPU)
CAS(等待期间消耗CPU)
synchronized的CPU层面实现
使用 lock + comxchg 实现
comxchg: CAS获取锁
lock: 执行代码块的时候,其他CPU不能访问该内存区域