Synchronized机制及锁膨胀
2022-07-02 21:11:00 2 举报
Synchronized是Java中用于实现线程同步的关键字,它通过锁机制来保证多线程环境下的数据安全。当一个线程访问一个对象的synchronized方法或代码块时,它会获取该对象的锁,其他线程必须等待锁释放后才能继续执行。但是,当多个线程频繁争夺同一把锁时,会导致锁膨胀现象,即锁的数量不断增加,从而影响程序性能。为了避免锁膨胀,可以使用更细粒度的锁或者使用无锁数据结构等技术来优化程序。
作者其他创作
大纲/内容
执行业务代码
无锁
自旋
自适应自旋锁是指,线程自旋的次数不再是固定的值,而是一个动态改变的值,这个值会根据前一次自旋获取锁的状态来决定此次自旋的次数。比如上一次通过自旋成功获取到了锁,那么这次通过自旋也有可能会获取到锁,所以这次自旋的次数就会增多一些,而如果上一次通过自旋没有成功获取到锁,那么这次自旋可能也获取不到锁,所以为了避免资源的浪费,就会少循环或者不循环,以提高程序的执行效率。简单来说,如果线程自旋成功了,则下次自旋的次数会增多,如果失败,下次自旋的次数会减少或者不自选,避免CPU空转。
对于 synchronized 关键字来说,它的自旋锁更加的“智能”,synchronized 中的自旋锁是自适应自旋锁
是否偏向锁
否
stw线程唤醒从安全点继续执行
获得轻量级锁
锁粗化
失败
轻量锁
偏向锁
偏向锁:因为经过大量的研究发现,大多数时候是不存在锁竞争的,经常是一个线程在持有锁,由于每次竞争锁会有很大的性能开销,为了降低锁的代价,从而引入了偏向锁
无用的锁,删除
lock中的markword标识的threadid是否当前线程(锁重入)
偏向锁撤销恢复无锁
判断锁状态
轻量级锁
Synchronized在多线程场景下,保证数据的一致性,线程的互斥性。(即:同一时刻只能有一个线程操作共享变量)
持有偏向锁线程暂停(stw)
持有偏向锁线程到达安全点
A线程访问代码块获取到锁对象之后,会在java对象头和栈帧记录偏向锁的请求线程threadid,因为偏向锁不会主动释放,所以之后A线程再次获取锁的时候,需要比较A线程的threadid是否和对象头中的线程id一致。1、一致:锁重入A线程继续获取锁,无需使用CAS加锁、解锁2、不一致: 说明有其它线程B来竞争锁对象,由于偏向锁不会主动释放锁对象,对象头存储的是线程A的threadid,那么需要对象头记录线程A是否存活: 1)如果没有存活,那么锁对象被重置为无锁状态(锁撤销),线程B设置偏向锁 2)如果存活,立即查找线程A的栈帧信息,如果线程A还需要继续持有锁,那么暂停线程A(stw),撤销A的偏向锁升级为轻量级锁,如果不需要继续持有锁,锁对象重置为无锁状态,锁偏向线程B
执行偏向锁撤销(锁竞争开始,释放锁的机制)
是否原持有偏向锁线程
在某些情况下,对于某段代码不存在竞争和共享的可能性,就会将这段代码的锁消除,达到提高性能的目的。
锁消除
是
获得轻量级锁执行业务代码
Synchronized锁有四种状态:无锁、偏向锁、轻量锁、重量锁。使用Synchronized后,随着线程的不断竞争会使得锁的状态不断发生变化,锁会升级但不会降级。但偏向锁可以被置为无锁状态(锁撤销)
检查持有偏向锁线程状态
某些情况下,一系列连续的加锁解锁操作,会导致不必要的性能开销,锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩大了范围。(增大了锁作用域,合并成一个更大的锁)
当前执行线程
继续尝试
锁升级(锁膨胀)
自适应自旋锁
重量级锁
markword存储指向线程栈中的LockRecord的指针
未退出同步代码块
JVM保证Synchronized 性能的提升的优化手段:锁膨胀、锁消除、锁粗化、CAS自适应自旋锁
markword存储指向堆中的monitor对象的指针
升级重量级锁
A线程获得轻量级锁之后,会把锁对象的对象头Markword复制一份到线程A的栈帧中创建用于存储锁记录的空间(DisplacedMarkword),然后使用CAS自旋把对象头中的内容替换为A线程存储锁记录的地址。如果A线程在复制对象头的过程中(cas替换前),C线程也获取锁,复制了锁对象头到C线程的锁记录空间,在执行CAS自旋做对象头内容替换的时候,发现A线程已经更改了对象头,则C线程替换对象头内容失败,尝试使用自旋锁等待A线程释放锁。自旋要消耗CPU会影响性能,所以不能无限制的自旋下去,因此自旋次数有限制,如果自旋次数到了,A线程还没有释放锁,C线程在自旋等待,D线程又进来竞争锁,这个时候轻量级锁会升级为重量级锁。重量级锁把除A线程之外的,前来竞争锁的线程全部阻塞,防止CPU空转。
成功
轻量锁:轻量锁考虑的是竞争锁的线程不是很多,且持有锁的时间短的场景。因为线程阻塞需要CPU从用户态切换到内核态,如果线程刚阻塞不久,锁就释放了,那这个代价有点大。所以干脆不阻塞这个线程,让它进行自旋等待锁释放。
指向重量级锁monitor指针
start
markword存储偏向锁的线程id
各种锁优缺点对比
jvm启动偏向锁默认打开,但是偏向锁启动是有延时的(4000ms)jvm参数 -XX:BiasedLockingStartupDelay=4000
线程非存活退出同步代码块
Synchronized锁对象:1、Synchronize修饰实例方法: 锁定当前对象2、Synchronize修饰静态方法: 锁定当前类的class对象3、Synchronize修饰同步代码块: 锁定当前代码块中的对象
自旋锁是指通过自身循环,尝试获取锁的一种方式。 优点在于它避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转入内核态,这个过程是比较慢的,所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。但是,如果长时间自旋还获取不到锁,那么也会造成一定的资源浪费,所以我们通常会给自旋设置一个固定的值来避免一直自旋的性能开销。
轻量锁/重量锁
连续使用做合并
重量锁
锁状态
优点
缺点
适用场景
加锁、解锁没有额外消耗。和执行非同步方法仅有纳秒级的差距
如果线程间存在锁竞争,会有额外锁撤销的性能消耗
基本没有线程竞争,一个线程访问同步的场景
竞争线程不会阻塞,适用cas自旋,提高了程序的响应速度
如果无法获取到锁,长时间自旋会消耗CPU性能
锁持有时间短,追求响应速度
线程竞争不使用自旋,不会导致cpu空转
线程阻塞,响应慢
追求吞吐量,锁持有时间长
当前线程挂起阻塞
CAS操作对象头markword锁记录指针指向当前线程锁记录
获得偏向锁记录threadid
到达一定自旋次数,失败
CAS操作更新markword标识的threadId
持有偏向锁线程栈中分配锁记录(lockRecord)拷贝锁对象头中的markword到持有偏向锁线程的lockRecord中
0 条评论
下一页