synchronized 的使用
类锁 和 对象锁
类锁: 基于当前类的Class加锁
对象锁:基于this对象加锁
public class Test {
public synchronized static void print () {
// 类锁
}
public synchronized void print2 () {
// 对象锁
}
}
synchronized 锁时基于对象实现的。 ,那么是如何是实现的吗?
我们需要先了解对象的内存布局。对象被分配在堆中,主要由这三部分组成:对象头,实例数据,补齐填充。对象头由MarkWord 和 class pointer 组成,锁便跟MarkWord 相关。
MarkWord的具体布局如下图所示:
不同的状态看不同的行;如果想看到对象在内存中的状态,可以增加如下maven依赖。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
<scope>provided</scope>
</dependency>
输出方式
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
001 , 意味着现在是无锁的状态。
synchronized 锁优化
jdk1.5时,并发编程大师引入了ReentrantLock 提升了锁的性能;
jdk1.6时,JDK团队对synchronized 做了大量优化,主要有以下三方面的优化:锁升级,锁消除,锁膨胀
锁消除
在 synchronized 修饰的同步方法或者同步代码块中,如果没有操作临界资源;就会触发锁消除。
锁膨胀
如果在一个循环中,频繁的获取和释放锁资源;这样带来的消耗很大;锁膨胀就是将锁的范围扩大,避免频繁的竞争和获取锁资源带来的不必要的消耗。
锁升级
TIPS:
synchronized 不存在从重量级锁降级到偏向锁或者轻量级锁。
synchronized 的四种锁状态。
-
无锁状态,匿名偏向锁状态:没有线程拿锁
-
偏向锁状态:没有线程竞争,只有一个线程拿锁资源
线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获得到锁资源。
-
轻量级锁状态:偏向锁出现竞争时,会升级成轻量级锁;(涉及到偏向锁撤销)
轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的,JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。
- 重量级锁状态:轻量级锁在CAS一段时间后,如果还没有拿到锁资源,就会升级成重量级锁。(其实CAS操作时在重量级锁时执行的)
重量级锁就是线程拿不到锁,就挂起。
偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态,只存在匿名偏向synchronized因为不存在从重量级锁降级到偏向或者是轻量。
synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点(tw)可以撤销,并发偏向锁撤销比较消耗资源
在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作
为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。
延迟启动时间默认是4。-XX:BiasedLockingStartupDelay=4
如果在开启偏向锁的情况下,查看对象,默认对象是匿名偏向。
重量级锁实现 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;
}
偏向锁会降级到无锁状态嘛?怎么降?
会,当偏向锁状态下,获取当前对象的hashcode值,会因为对象头空间无法存储hashcode,导致降级到无锁状态。
- 本文链接: https://www.sunce.wang/archives/这一次肝了多线程与高并发之深入synchronized
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!