当前位置:网站首页>02 多线程与高并发 - synchronized 解析
02 多线程与高并发 - synchronized 解析
2022-07-30 06:10:00 【小刘说】
CAS
compare and swap,比较和交换
线程基于 CAS 修改数据的方式:先获取主内存数据,在修改之前,先比较数据是否一致,如果一致修改主内存数据,如果不一致,放弃这次修改
存在的问题
- CAS 只能对一个变量的修改实现原子性,一段代码中多个变量则无法保证未经 CAS 修饰的变量的原子性
- 存在 ABA 问题(解决方案:加版本号)
- CAS 执行次数过多,但是依旧无法实现对数据的修改,CPU 会一直调度这个线程,造成对 CPU 的性能损耗(轻量锁不一定比重量锁好的原因)
对象在内存中的布局
- MarkWord 对象标记:
包含一系列的标记位,比如轻量级锁的标记位、偏向锁标记位、gc记录信息等等。在32位系统占4字节,在64位系统中占8字节。 - Class Point:
用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。 - 实例数据(对象里面的属性)
- 补齐填充(会补齐为 8 的整数倍,
缓存行对齐
)
在 java 中查看对象布局
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
MarkWord 信息
上面图片中红色框记录了MarkWord 的信息,看标志位 01
对照上面的表可以看到当前对象处于无锁态
synchronized 锁升级
偏向锁是否启动
:偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态- synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点,stw,才可以撤销,并发偏向锁撤销比较消耗资源
- 在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作,
- 为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。
// 关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
无锁态、匿名偏向
:没有线程拿锁。偏向锁
:没有线程的竞争,只有一个线程再获取锁资源。
线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获得到锁资源。轻量级锁
:偏向锁出现竞争时,会升级到轻量级锁(触发偏向锁撤销)。一个线程竞争到了(记录Lock Record)其他线程开始自旋
轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的,JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。重量级锁
:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁(其实CAS操作是在重量级锁时执行的)。
重量级锁就是线程拿不到锁,进入等待队列,不需要消耗 CPU 资源。
ObjectMonitor
涉及 ObjectMonitor 一般是到达了重量级锁才会涉及到。在到达重量级锁之后,重量级锁的指针会指向ObjectMonitor 对象。
ObjectMonitor() {
_header = NULL;
_count = 0; // 抢占锁资源的线程个数
_waiters = 0, // 调用wait的线程个数。
_recursions = 0; // 可重入锁标记,
_object = NULL;
_owner = NULL; // 持有锁的线程
_WaitSet = NULL; // wait的线程 (双向链表)
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ; // 假定的继承人(锁释放后,被唤醒的线程,有可能拿到锁资源)
_cxq = NULL ; // 挂起线程存放的位置。(单向链表)
FreeNext = NULL ;
_EntryList = NULL ; // _cxq会在一定的机制下,将_cxq里的等待线程扔到当前_EntryList里。 (双向链表)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
其他概念
编译器优化:锁消除
:线程在执行一段 synchronized 代码块时,发现没有共享数据的操作,自动帮你把 synchronized 去掉。锁膨胀
:在一个多次循环的操作中频繁的获取和释放锁资源,synchronized 在编译时,可能会优化到循环外部。
相关问题
- 单例模式下DCL是否需要加 volatile (半初始化,指令重排)
需要
class T{
int n = 1;
}
T t = new T();
正常创建过程:
(1)申请一块内存空间,new 对象,此时 n 为初始值 0
(2)调用构造方法,n = 1
(3)t 与 新建的对象建立关联
申请内存,初始化,关联是正常顺序,如果CPU对指令重排,可能会造成
申请内存,关联,初始化,在还没有初始化时,其他线程来获取数据,导致获取到的数据虽然有地址引用,但是内部的数据还没初始化,都是默认值,导致使用时,可能出现与预期不符的结果
//DCL 双重校验加锁
public class Single {
private volatile static Single instance ; //volatile 修饰,防止指令重排
private Single(){
}
public static Single getInstance(){
if(instance==null){
synchronized (Single.class){
if(instance==null){
instance = new Single();
}
}
}
return instance;
}
}
边栏推荐
- Oracle查看表空间使用率及爆满解决方案
- 解决datagrip连接sqlserver报错:[08S01] 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。
- export , export default, import complete usage
- 包含min函数的栈(js)
- The first artificial intelligence safety competition officially launched
- 华为发布“十大发明”,包含计算、智能驾驶等新领域
- 适合程序员的输入法
- redis实现分布式锁的原理
- UDP和TCP使用同一个端口,可行吗?
- C# 使用RestSharp 实现Get,Post 请求(2)
猜你喜欢
随机推荐
分布式系统中的开创者—莱斯利·兰伯特
2020 ACM | MoFlow: An Invertible Flow Model for Generating Molecular Graphs
Mobile phone side scroll to page to specify location
AI can identify race from X-rays, but no one knows why
Playing script killing with AI: actually more involved than me
分布式锁开发
go : 使用 grom 删除数据库数据
Input method for programmers
go : go-redis list operation
ArrayList
Go 使用mencached缓存
go : delete database data using grom
assert
MySql详解基础
万能js时间日期格式转换
roslyn folder under bin folder
MYSQL 主从恢复锁表后, 处理SQL 线程锁解决.
2020年度总结——品曾经,明得失,展未来
雷总个人博客看到
export , export default,import完整用法