synchronized 的使用

类锁 和 对象锁

类锁: 基于当前类的Class加锁

对象锁:基于this对象加锁

public class Test {

  public synchronized  static void print () {
     // 类锁
  }

  public   synchronized   void print2 () {
  	// 对象锁
  }

}

synchronized 锁时基于对象实现的。 ,那么是如何是实现的吗?

我们需要先了解对象的内存布局。对象被分配在堆中,主要由这三部分组成:对象头,实例数据,补齐填充。对象头由MarkWord 和 class pointer 组成,锁便跟MarkWord 相关。

MarkWord的具体布局如下图所示:

image-1672147760131

不同的状态看不同的行;如果想看到对象在内存中的状态,可以增加如下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());
 }

image-1672148624286

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,导致降级到无锁状态。