Java并发编程(二)synchronized

118 阅读4分钟

1.synchronized的使用方式

  • synchronized方法
public class Sync {  
    public synchronized void sync() {  
        // do something
    }  
}
  • synchronized代码块
public class Sync {  
    public void sync() {  
        synchronized(this){  
        // do something  
        }  
    }  
}

类锁和对象锁

  • 类锁:基础类的Class对象加锁(当synchronized修饰静态方法时是使用当前类的Class加锁)

  • 对象锁:基于new出的对象加锁(当synchronized修饰普通方法时是使用this对象加锁)

2.synchronized的实现

synchronized锁是基于对象实现的! synchronized是基于对象实现的互斥锁,了解synchronized的实现需要先了解对象在内存中是如何存储的。

image.png

64位操作系统中 image.png

在Java中查看。

导入依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

查看对象信息

image.png

初始化的对象是无锁状态

3.synchronized锁升级

synchronized在jdk1.6之前,一直是重量级锁:只要线程获取锁资源失败,直接挂起线程(用户态-内核态)

JDK团队在jdk1.6对synchronized进行了以下优化

锁升级:

  • 无锁状态、匿名偏向状态:没有线程拿锁。
  • 偏向锁状态:没有线程的竞争,只有一个线程再获取锁资源。 线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获得到锁资源。
  • 轻量级锁:偏向锁出现竞争时,会升级到轻量级锁(触发偏向锁撤销)。 轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的,JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。
  • 重量级锁:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁(其实CAS操作是在重量级锁时执行的)。 重量级锁就是线程拿不到锁,就挂起。

偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态,不存在从重量级锁降级到偏向或者是轻量,只存在匿名偏向synchronized。

synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点,stw,才可以撤销,并发偏向锁撤销比较消耗资源 在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作, 为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。 如果在开启偏向锁的情况下,查看对象默认对象是匿名偏向。

编译器优化:

锁消除:线程在执行一段synchronized代码块时,发现没有共享数据的操作,自动帮你把synchronized去掉。

锁膨胀:在一个多次循环的操作中频繁的获取和释放锁资源,synchronized在编译时,可能会优化到循环外部。

PS:

  • 当一个对象已经计算过identity hash code,它就无法进入偏向锁状态;
  • 当一个对象当前正处于偏向锁状态,并且需要计算其identity hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为轻量级锁或者重量锁;
  • 轻量级锁的实现中,会通过线程栈帧的锁记录存储Displaced Mark Word;重量锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word(volatile markOop _header;),其中可以存储identity hash code的值。

4.synchronized-ObjectMonitor

涉及ObjectMonitor一般是到达了重量级锁才会涉及到。

在到达重量级锁之后,重量级锁的指针会指向ObjectMonitor对象。

hg.openjdk.java.net/jdk8u/jdk8u…

  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;
  }