前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >synchronized源码分析

synchronized源码分析

作者头像
用户6182664
发布2020-05-07 23:35:51
3920
发布2020-05-07 23:35:51
举报

1.使用方法

synchronized是一种隐式锁

(1)修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

代码语言:java
复制
public synchronized void method1(){
         System.out.println("Method 1 start");
         try {
             System.out.println("Method 1 execute");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }

(2)修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

代码语言:java
复制
public class SynchronizedTest {
      public static synchronized void method1(){
          System.out.println("Method 1 start");
          try {
              System.out.println("Method 1 execute");
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Method 1 end");
      }

(3)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

代码语言:java
复制
public class SynchronizedTest {
     public void method1(){
         System.out.println("Method 1 start");
         try {
             synchronized (this) {
                 System.out.println("Method 1 execute");
                 Thread.sleep(3000);
             }
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }

2.实现原理

synchronized可以保证变量的原子性,可见性和顺序性,所以可以保证方法或者代码块在运行时只有一个方法可以进入临界区获取资源,同时还可以保证内存变量的内存可见性。并且synchronized是一个可重入锁

synchronized实现原理:

(1)字节码层面

synchronized是基于进入和退出管程(Monitor)对象实现(monitorenter和monitorexit), monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块结束的位置,任何一个对象都有一个Monitor与之相关联,当一个线程持有Minitor后,它将处于锁定状态。

(2)JVM层面实现

在JVM级别是通过编译后access_flags标志位(ACC_SYNCHRONIZED)来表示是否使用了synchronized

3.synchronized的优化

synchronized在JDK1.6之前是重量级锁,在JDK1.6以后对synchronized做了优化,增加了偏向锁,轻量级锁,锁粗化,锁消除,适应性自旋等操作,大大增加了synchronized的效率。

(1)Mark Word

synchronized用的锁是存在Java对象头里的,HotSpot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)和Klass Pointer(类元数据指针),其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。下图是Java对象头的存储结构(32位虚拟机),Mark Word为一个非固定的数据结构。

(2)偏向锁

原因:按照之前HotSpot的设计,每次加锁/解锁都会涉及到一些CAS操作,但是实验发现大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程获得。

目的:为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的加锁解锁操作是需要依赖多次CAS原子操作,减少CAS操作则可以大大增加系统的执行效率

流程:(1)检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01

(2)若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行同步代码块,否则则通过CAS操作竞争锁,如果竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则通过CAS竞争失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块

(3)轻量级锁

目的:在多线程的竞争下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗

流程:

1. 判断当前对象是否处于无锁状态(hashcode、0、01),若是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word);否则执行步骤(3);

2. JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指正,如果成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作;如果失败则执行步骤(3);

3. 判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态;

4.从C++源码看synchronized

(1)C++源码中监视器锁(Monitor)的数据结构

oopDesc–继承–>markOopDesc–方法monitor()–>ObjectMonitor–>enter、exit 获取、释放锁

(2)ObjectMonitor类

在HotSpot虚拟机中,最终采用ObjectMonitor类实现monitor。

openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp源码如下:

代码语言:java
复制
ObjectMonitor() {
    _header = NULL;//markOop对象头
    _count = 0;
    _waiters = 0,//等待线程数
    _recursions = 0;//重入次数
    _object = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
    _owner = NULL;//指向获得ObjectMonitor对象的线程或基础锁
    _WaitSet = NULL;//处于wait状态的线程,会被加入到wait set;
    _WaitSetLock = 0 ;
    _Responsible = NULL ;
    _succ = NULL ;
    _cxq = NULL ;
    FreeNext = NULL ;
    _EntryList = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
    _SpinFreq = 0 ;
    _SpinClock = 0 ;
    OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
    _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
}

每个线程都有两个ObjectMonitor对象列表,分别为free和used列表,如果当前free列表为空,线程将向全局global list请求分配ObjectMonitor。ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表

(3)获取锁的流程

参考:synchronized的C++分析

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java程序员那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档