并发编程-synchronized锁升级原理分析
1、对象内存分布(此时说的对象为堆中开辟的内存)
对象包含:对象头(Mark Word,Klass Pointer)、实例数据、对齐填充
Mark Word:存储对象的锁状态(001无锁,101偏向锁,00轻量级锁,10重量级锁),偏向锁线程ID,轻量级锁LR指针,HashCode,GC分代年龄等,这些数据在32位和64位的虚拟机中的长度分别为4字节、8字节。
Klass Pointer:类的元数据指针,虚拟机通过这个指针确定对象是哪个类的实例,占用内存大小32位虚拟机和64位虚拟机(默认开启指针压缩)分别为4字节和4字节(使用-XX:-UseCompressedOops关闭指针压缩则为8字节)
实例数据; 包含类的属性和父类的属性信息
对齐填充:当Mark Word+Klass Pointer+实例数据的字节大小不是8的整数倍时(虚拟机要求对象起始地址必须是8的整数倍),则填充对应的字节大小

2、使用JOL工具查看对象内存布局
JOL工具依赖
<!-- 查看Java 对象布局、大小工具-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
锁升级流程图:

a、无锁状态
public static void main(String[] args) throws InterruptedException { Object lock = new Object(); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } //lock对象内存布局打印 java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word#锁状态 #锁状态#001代表锁状态为无锁 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) //对象头#Mark Word#Klass Pointer #指针表示对象是哪个类的实例(指向运行时常量池里的class代码) //32位虚拟机Klass Pointer指针占用内存大小为4字节 //64位虚拟机开启指针压缩并且内存<32G则Klass Pointer占用内存大小为4字节 //64位虚拟机内存>32G或关闭指针压缩(-XX:-UseCompressedOops)则占用内存大小为8字节 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) //实例数据 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) //对齐填充(Mark Word+Klass Pointer+实例数据!=8字节的整数倍,将自动填充) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
b、偏向锁-可偏向状态/匿名偏向==>偏向锁-偏向状态
public static void main(String[] args) throws InterruptedException { Thread.sleep(4100); Object lock = new Object(); //虚拟机启动4秒后,锁状态变为偏向锁-可偏向状态 System.out.println(ClassLayout.parseInstance(lock).toPrintable()); synchronized (lock){ System.out.println("//====================进入synchronized后打印===================="); ////进入synchronized代码块后,锁状态由偏向锁-可偏向 变为 偏向锁-偏向 System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } } java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //#对象头#Mark Word #101 偏向锁 00000000 00000000 00000000可偏向,线程id为0 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total //====================进入synchronized后打印==================== java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //#对象头#Mark Word #101 偏向锁 01000000 11110100 00000010 偏向的线程id 0 4 (object header) 05 40 f4 02 (00000101 01000000 11110100 00000010) (49561605) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
c、偏向锁-可偏向状态/匿名偏向==>偏向锁-偏向状态==>轻量级锁
public class TestSynchronized { public static void main(String[] args) throws InterruptedException { Thread.sleep(4100); Object lock = new Object(); TestSynchronized testSynchronized = new TestSynchronized(); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); testSynchronized.syncLayout(lock); for (int i = 0; i < 1; i++) { new Thread(new Runnable() { @Override public void run() { try { testSynchronized.syncLayout(lock); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } public void syncLayout(Object lock) throws InterruptedException { synchronized (lock){ Thread.sleep(1500); System.out.println("=============当前线程==========="+Thread.currentThread().getId()); System.out.println(ClassLayout.parseInstance(lock).toPrintable()); } } } //对象内存布局打印 java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 00000000 00000000 00000000表示线程id为0,偏向锁-可偏向 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============当前线程===========1 java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 01000000 10000100 00000010表示线程id,偏向锁-偏向 0 4 (object header) 05 40 84 02 (00000101 01000000 10000100 00000010) (42221573) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============当前线程===========12 java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #00轻量级锁 11110100 10001110 00011111表示线程栈中Lock Record对象指针 0 4 (object header) 40 f4 8e 1f (01000000 11110100 10001110 00011111) (529462336) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
d、两个线程轻微竞争下,偏向锁-偏向状态==>轻量级锁
public class TestSynchronized { public static void main(String[] args) throws InterruptedException { Thread.sleep(4100); Object lock = new Object(); TestSynchronized testSynchronized = new TestSynchronized(); new Thread(new Runnable() { @Override public void run() { try { testSynchronized.syncLayout(lock); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); Thread.sleep(1000); new Thread(new Runnable() { @Override public void run() { try { testSynchronized.syncLayout(lock); } catch (InterruptedException e) { e.printStackTrace(); } } },"t2").start(); } public void syncLayout(Object lock) throws InterruptedException { System.out.println("=============线程:"+Thread.currentThread().getName()+"进入锁前=========\n"+ClassLayout.parseInstance(lock).toPrintable()); synchronized (lock){ System.out.println("=============线程:"+Thread.currentThread().getName()+"执行中=========\n"+ClassLayout.parseInstance(lock).toPrintable()); } System.out.println("=============线程:"+Thread.currentThread().getName()+"释放锁后=========\n"+ClassLayout.parseInstance(lock).toPrintable()); } } //打印对象内存布局 =============线程:t1进入锁前========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 00000000 00000000 00000000线程id为0,可偏向状态 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============线程:t1执行中========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 10101000 01000110 00010111 偏向状态 0 4 (object header) 05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============线程:t1释放锁后========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 10101000 01000110 00010111 偏向状态 0 4 (object header) 05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============线程:t2进入锁前========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #101偏向锁 10101000 01000110 00010111 偏向状态 0 4 (object header) 05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============线程:t2执行中========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #00轻量级锁 11101111 01110110 00011000 Lock Record指针 // hashcode会存储到Lock Record中,而Lock Record会存储到栈中 0 4 (object header) 90 ef 76 18 (10010000 11101111 01110110 00011000) (410447760) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total =============线程:t2释放锁后========= java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE //对象头#Mark Word #01无锁 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
3、synchronized是管程在jvm级别的实现
//=================================无锁========================================//
虚拟机启动之后,对象的锁状态默认为无锁状态
//================================偏向锁=======================================//
虚拟机默认锁延迟,虚拟机启动5秒后,对象锁状态变为匿名偏向锁
偏向锁加锁过程:
线程1到达synchronized(obj),判断锁对象状态为偏向锁(101 ),并且Object的MarkWord的ThreadId是否为null
为null,cas设置ThreadId为当前线程id:
cas成功,偏向锁偏向成功。
cas失败,说明其他线程获得了偏向锁,存在锁竞争,需要撤销已获得偏向锁的线程,并把对象锁状态升级为轻量级锁(此操作需要等到安全点,也就是GC的STW才能执行) 。
不为null,则判断线程id是否为当前线程:
是,则为重入锁,在线程栈中创建LockRecord,cas设置displaced word为null,对象引用指向Object,更新Object的MarkWord指向新的LockRecord地址。
否,说明其他线程获得了偏向锁,存在锁竞争,需要撤销已获得偏向锁的线程,并把对象锁状态升级为轻量级锁(此操作需要等到全局安全点,也就是GC的STW才能执行)。
偏向锁撤销,并升级为轻量级锁的具体过程 : 偏向锁撤销,更新锁对象状态为01,ThreadId为null,在线程栈创建LockRecord,复制Object的MarkWord到LR的displaced word,设置对象引用指向Object,更新Object的MarkWord指向LR地址 。
轻量级锁适用线程轻微竞争的情况
轻量级锁加锁过程:
轻量级锁解锁变为无锁状态
线程到达synchronized(obj)后,判断对象锁状态是否为无锁状态(001)
是,当前线程创建LockRecord,cas设置displaced word为markWord,更新对象引用指向Object,更新markword指针指向LockRecord地址,成功则返回
否,则判断markword指针是否为null
是,当前线程创建LockRecord,cas设置displaced word为markword,更新对象引用指向Object,更新markword为LockRecord地址
否,则判断markWord指针是否为当前线程
是,则为重入锁,当前线程创建LockRecord,设置displaced word为null,更新对象引用指向Object,更新markWord指针指向LockRecord地址,成功则返回
否,则存在锁竞争,锁膨胀获取moniter对象(),执行moniterenter逻辑,执行cas获取锁,也就是设置_owner为当前线程,成功则返回,失败则继续执行多次自适应自旋+cas,成功则返回,再失败则入队_cxq,调用ParkEvent.park()线程进入BLOCKED状态,等待Trigger.unpark()唤醒
轻量级锁解锁过程:
取出栈中LockRecord的displaced word,判断是否为空
是,则为重入锁,重入次数减1,删除锁记录LockRecord。
否,cas将displaced word设置回markword,成功则解锁成功,失败存在竞争则开始膨胀,进入重量级锁解锁流程。

//============================升级重量级锁=================================//
重量级锁适用于多线程出现激烈激烈竞争情况
重量级锁加锁过程:
判断对象是否为无锁状态(001)
是,则获取moniter对象(cas设置_header为markword,_owner为null,对象引用指向Object),设置markword指针指向moniter,执行moniter的enter逻辑
否,直接执行moniter的enter逻辑
moniter的enter逻辑:cas抢占锁也就是更新_owner为当前线程,成功则返回,失败则继续执行多次自适应自旋cas抢占锁,当自适应失败达到一定次数后,则入队_cxq,调用ParkEvent.park()挂起当前线程(线程状态为BLOCKED,注意调用ParkEvent.park()前获取到锁并不会出现用户态和内核态的切换,性能消耗不大),等待Trigger.unpark()唤醒
判断_owner是否为当前线程
是,更新_owner为null,释放锁,调用Trigger.unpark唤醒_waitSet或_cxq队列里的线程
否,则其他线程占用该锁,直接返回

//============================调用Object.hashCode===========================//
//============================调用Object.wait================================//
//============================调用Object.notify/notifyAll=========================//

4、锁粗化(对同一个对象反复加锁解锁,甚至加锁操作是出现在循环体中的,如果JVM检测到一连串零碎操作是针对同一对象的加锁,将会扩大锁同步范围到整个操作序列的外部)
public class TestSynchronized { StringBuffer stringBuffer = new StringBuffer(); public void append(){ //此时的appeng方法是synchronized修饰的,因此存在多次加锁解锁的过程 //此时JVM检测到对同一个对象加锁解锁,就会将其合并成范围更大的加锁解锁 stringBuffer.append("a").append("b"); } }
5、锁消除(虚拟机在JIT编译期间,通过上下文扫描,去除不可能存在共享资源竞争的锁,通过锁消除,节省毫无意义的锁请求时间)
public void append(){ //因为stringBuffer是局部变量,因此不存在线程竞争 StringBuffer stringBuffer = new StringBuffer(); //此时的appeng方法是synchronized修饰的,因此存在多次加锁解锁的过程 //但是此时的stringBuffer对象是局部变量,不存在资源竞争,因此可以把append上的synchronized消除 stringBuffer.append("a").append("b"); }
参考链接:https://blog.csdn.net/cx201125/article/details/130060155

浙公网安备 33010602011771号