synchronized底层实现及优化机制
Posted 随心所向李先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized底层实现及优化机制相关的知识,希望对你有一定的参考价值。
synchronized底层实现及优化机制
前言
首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。
jdk1.6之前
可以看到synchronized加锁加的是内部的monitor对象,monitor是操作系统内部的对象,当多个线程一起访问synchronized修饰的代码块时,如果线程1先执行加锁的操作,然后执行业务方法,其他线程就会加不成功,然后阻塞在一个队列中,排队等待。
因为整个过程会发生线程阻塞、上下文切换、操作系统线程调度等,对性能有很大的影响。所以有了后面版本的改进
jdk1.6之后
引入了锁的几种状态分别是无状态、偏向锁、轻量级锁、重量级锁,其中偏向锁是1.6后才有的状态。线程锁会有个升级过程
下面来看下锁升级过程:
默认是无状态,
当一个线程访问synchronized修饰的代码块,首先看是否开启偏向锁,jdk默认是系统启动4秒后开启的。这个可以自己修改
如果开启了那么把当前线程id,放到对象头里,如果未开启则直接进入轻量级锁
当有第二个线程进来时候 线程从偏量锁到轻量级自旋锁
当自旋到一定次数 线程进入重量级锁(这个由jdk自己判断,由前一次同一个锁的自旋时间和锁的状态决定)
轻量级锁就是cas这种线程不会阻塞持续消耗着cpu, 重量级锁就是1.6之前那种线程会阻塞到一个队列中,那么偏向锁是什么呢?
简单来说就是把一个线程的id写到对象头里,下次这个线程再过来的时候就会判断这个id是否一样如果一样加锁成功,这就是偏向锁。
为什么会有这个概念或者状态呢,因为hotspot开发者经过调查发现我们的程序有一多半的时间都是一个线程在跑,这样我们就没有上来就开启重量级锁,因为开启重量级锁需要很多的操作,很消耗性能。
那么为什么轻量级锁在线程并发越来越多的时候要升级重量级锁呢,因为自旋锁的原理当加锁失败他不会切换线程它会不停地再次地尝试获取锁会持续消耗着cpu,会越来越耗费资源,到一定程度会超过线程切换的开销。
要点:整个过程是不可逆的 就是不能从重量级锁到轻量级 到偏量锁。
synchronized的整个升级过程都是对 对象头的操作,下面我们来看看对象的内部结构是怎么样的,
对象结构以及升级后的内部变化:
首先我们创建一个对象:
import lombok.Data;
@Data
public class Tcc
private Integer id;
private String name;
再来看下这个结构图
1、实力数据就是我们在对象中创建的变量如id,name这些,这个很好理解。
2、对象头的话分为两部分MetaData元数据指针指向元数据占4个byte 这个这篇不做过多解释,和我们的锁无关,我们对象锁的信息存在对象头的Mark Word,它占8个byte,也就是64个bit,这64个bit会随着锁的升级而变化。数组长度是数组才有的部分,对象中没有。
3、对象填充就是当我们的对象整体size 不是8的倍数的话就会填充一些空间,以达到8的倍数,这样做是 为了查询效率牺牲空间换取性能。
为了方便大家的理解,我写个demo,来验证这些,首先我们需要导入一个依赖,这个依赖能够打印出我们的对象结构信息
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
代码:
public static void main(String[] args)
Tcc tcc = new Tcc();
System.out.println("无状态对象内部结构" + ClassLayout.parseInstance(tcc).toPrintable());
Tcc tcc1 = new Tcc();
Thread.sleep(5000);
System.out.println("开启偏向锁后" + ClassLayout.parseInstance(tcc1).toPrintable());
synchronized (tcc1)
System.out.println("升级偏向锁后" + ClassLayout.parseInstance(tcc1).toPrintable());
Thread.sleep(1000);
new Thread(() ->
synchronized (tcc1)
System.out.println("升级轻量级锁后" + ClassLayout.parseInstance(tcc1).toPrintable());
try
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
).start();
Thread.sleep(1000);
new Thread(() ->
synchronized (tcc1)
System.out.println("升级重量级锁后" + ClassLayout.parseInstance(tcc1).toPrintable());
).start();
运行结果:
无状态对象内部结构com.teamer.servicetm.service.impl.Tcc object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
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) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int Tcc.id 0
16 4 java.lang.String Tcc.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
开启偏向锁后com.teamer.servicetm.service.impl.Tcc object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
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) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int Tcc.id 0
16 4 java.lang.String Tcc.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
升级偏向锁后com.teamer.servicetm.service.impl.Tcc object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 34 02 (00000101 00111000 00110100 00000010) (36976645)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int Tcc.id 0
16 4 java.lang.String Tcc.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
升级轻量级锁后com.teamer.servicetm.service.impl.Tcc object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) c8 f4 0f 1d (11001000 11110100 00001111 00011101) (487584968)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int Tcc.id 0
16 4 java.lang.String Tcc.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
升级重量级锁后com.teamer.servicetm.service.impl.Tcc object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) da c9 48 19 (11011010 11001001 01001000 00011001) (424200666)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 int Tcc.id 0
16 4 java.lang.String Tcc.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
简单解释下 offset-起始位 size-所占的大小单位是byte type-类型(前两行是markword)value-前边是16进制后边是2进制bit,我们重点来看下这个value前两行这8个byte 64bit是怎么变化的,看的时候,需要倒着看也就是倒数第一个byte是第一个byte
无状态-最后三位 是001 第一个0代表 未开启偏向锁 01代表 是锁的标志位
偏向锁-最后三位 101 此时倒数第三位由0变成1锁标志位依然是01 不过前边的bit很多都变了,因为前边的bit存放了线程id
轻量级锁-最后三位 000 锁的标志位变成00可以看到虽然升级轻量级锁线程id信息不会清除
重量级锁-最后三位010 锁的标志位变成10
本文参考b站诸葛老师讲解,码字画图不易,欢迎大家一起讨论一起交流,点赞关注转发
以上是关于synchronized底层实现及优化机制的主要内容,如果未能解决你的问题,请参考以下文章
Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力