synchronized
synchronized的用法参考Java中使用同步关键字synchronized需要注意的问题 - @ 小浩 - 博客园
synchronized是非公平锁
synchronized实现原理
16_深入JVM源码-monitor竞争_哔哩哔哩_bilibili
9 synchronized与锁 · 深入浅出Java多线程 (redspider.group)
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: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=2, locals=0, args_size=00: getstatic #4 // Field sum:I3: iconst_14: iadd5: putstatic #4 // Field sum:I8: returnLineNumberTable:line 17: 0line 18: 8public synchronized void minus();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic #4 // Field sum:I3: iconst_14: isub5: putstatic #4 // Field sum:I8: returnLineNumberTable:line 21: 0line 22: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 this Lorg/cc/lipiao/demo/synchronizedDemo/SynchronizedDemo;public void add2();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: aload_01: getfield #3 // Field obj:Ljava/lang/Object;4: dup5: astore_16: monitorenter7: getstatic #4 // Field sum:I10: iconst_211: iadd12: putstatic #4 // Field sum:I15: aload_116: monitorexit17: goto 2520: astore_221: aload_122: monitorexit23: aload_224: athrow25: return
可以看到当关键字用在方法时,字节码使用ACC_SYNCHRONIZED,当关键字用在代码块时,字节码使用monitorenter和monitorexit指令
java对象头和锁的状态
锁状态存储在对象头的mark word


总结下来就是:无锁时: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等等