synchronized
synchronized的用法参考Java中使用同步关键字synchronized需要注意的问题 - @ 小浩 - 博客园
synchronized是非公平锁
synchronized实现原理
16_深入JVM源码-monitor竞争_哔哩哔哩_bilibili
Java中的锁——Lock和synchronized - 夏末秋涼 - 博客园 (cnblogs.com)
原理总结
1,synchronized代码块基于进入和退出monitor对象实现。代码编译后将monitorenter指令插入同步代码块的前面,monitorexit指令插入同步代码块的后面,发生异常时也会执行monitorexit指令
2,synchronized方法读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的
3,synchronized用的锁存储在对象头中的markword,markword中的锁标记指向的是monitor对象,
锁标记位(无锁01、轻量级锁00、重量级锁10、偏向锁01)
4,monitor对象有两个队列(EntryList、WaitSet)以及锁持有者Owner标记
synchronized的两种用法以及他们对应的字节码
package org.cc.lipiao.demo.synchronizedDemo;
public class SynchronizedDemo {
private static int sum;
private Object obj = new Object();
public static synchronized void add(){
sum++;
}
public synchronized void minus(){
sum--;
}
public void add2(){
synchronized (obj){
sum += 2;
}
}
}
public static synchronized void add();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #4 // Field sum:I
3: iconst_1
4: iadd
5: putstatic #4 // Field sum:I
8: return
LineNumberTable:
line 17: 0
line 18: 8
public synchronized void minus();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field sum:I
3: iconst_1
4: isub
5: putstatic #4 // Field sum:I
8: return
LineNumberTable:
line 21: 0
line 22: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lorg/cc/lipiao/demo/synchronizedDemo/SynchronizedDemo;
public void add2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field sum:I
10: iconst_2
11: iadd
12: putstatic #4 // Field sum:I
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
可以看到当关键字用在方法时,字节码使用ACC_SYNCHRONIZED,当关键字用在代码块时,字节码使用monitorenter和monitorexit指令
java对象头和锁的状态
锁状态存储在对象头的mark word
![](https://i-blog.csdnimg.cn/blog_migrate/b0c8e9ec736f38e85027924ca15277c5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ea6e5cf71e5551ff2d59278ec944a5c8.png)
总结下来就是:无锁时:0 01
偏向锁:线程id epoch 1 01
轻量级锁:指向栈中lock record的指针 00
重量级锁:指向互斥量的指针 10
锁状态
偏向锁
大多数情况下,锁总是由同一个线程多次获得
偏向锁的优势:减少同一线程获取锁的代价(线程进入或退出同步代码块时只需要检查偏向锁、锁标志位和线程ID即可)
在1.6之后是默认开启的,并且是在应用程序启动几秒之后开启
偏向锁处理流程:
1、第一次进入同步块并获取锁时,虚拟机将对象头偏向锁设为1,同时CAS操作(将线程ID记录到Mark Word中)
2、后面再有线程进入这个同步块时,比较当前线程的threadID和mark word中的threadID是否一致,如果一致那么无须使用CAS
3、如果threadID不一致,那么需要查看mark word中记录的线程1是否存活
4、如果没有存活,那么mark word被重置为无锁状态,当前线程可以竞争将其设置为偏向锁
5、如果存活,那么立刻查找线程1的栈帧信息,如果还是需要继续持有这个锁,那么暂停线程1,撤销偏向锁,升级为轻量级锁
6、如果线程1 不再使用该锁,那么将mark word设为无锁状态,重新偏向新的线程。
轻量级锁
1、线程1获取轻量级锁时会先把MarkWord复制一份到线程1的栈帧中的DisplacedMarkWord(用于存储锁记录的空间),然后使用CAS把对象头中的内容替换为DisplacedMarkWord的地址
2、如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。
3、如果自旋到了一定次数线程2还在自旋等待或者这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
重量级锁
TODO
JVM对synchronized的优化
锁消除:TODO
锁粗化:TODO
锁膨胀:无锁-》偏向锁-》轻量级锁-》重量级锁
平时写代码如何优化synchronized
减少synchronized的范围,减少synchronized的代码
降低synchronized的粒度:使用CurrentHashMap替代Hashtable等等