Synchronized底层实现原理

文章目录

    • Synchronized底层实现原理
      • 一、概述
        • 1、Synchronized有什么用处?
        • 2、Synchronized如何使用?
        • 3、Java锁相关
      • 二、实现原理
        • 1、jvm基于进入和退出Monitor对象来实现方法同步和代码块同步。
        • 2、Java对象头
        • 3、锁优化

Synchronized底层实现原理

一、概述

1、Synchronized有什么用处?

​ 原子性:synchronized保证语句块内操作是原子的

​ 可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)

​ 有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

​ 相关锁类型:具有可重入锁、独占锁、非公平锁、悲观锁。

2、Synchronized如何使用?

​ 修饰实例方法,对当前实例对象加锁。

/**
 * synchronized 修饰实例方法
 */
public synchronized void increase(){
    i++;
}

​ 修饰静态方法,多当前类的Class对象加锁。

/**
 * synchronized 类对象加锁
 */
public static synchronized void increase(){
    i++;
}

​ 修饰代码块,对synchronized括号内的对象加锁。

/**
 * synchronized 实例对象锁类型--修饰代码块
 */
public void increase(){
    synchronized(this) {
        i++;
    }
}

 /**
  * synchronized 类对象锁类型--修饰代码块
  */
public void increase(){
    synchronized(Test.class) {
         i++;
    }
}

3、Java锁相关

​ 参考[美团技术文档—Java锁]

二、实现原理

1、jvm基于进入和退出Monitor对象来实现方法同步和代码块同步。

​ 方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。

​ 代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。

2、Java对象头

​ HotSpot虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

img

​ 对象头:主要包括两部分:Mark Word、类型指针(Class Metadata Address);当对象为数组对象时,有一个Array Length。

​ Mark Word:本质上是堆当中的一段内存区域,32/64位机器的长度分别时32/64位。32为图解如下:

Synchronized底层实现原理_第1张图片

Class Metadata Address:指向方法区中的元数据的指针。

​ 实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

​ 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

3、锁优化

​ Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。

​ Java 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”:锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。1.6版本以后,Java默认开始偏向锁。

偏向锁:只有一个线程抢锁,可获取到偏向锁。记录当前线程ID,不解锁。优点:单个线程时拥有更好的性能。(jdk1.6引入)

轻量级锁:当有两个线程争抢锁的时候,偏向锁升级成轻量级锁。CAS操作自旋尝试获取锁。优点:保持线程running状态,不释放cpu执行时间片,减少CPU线程切换的耗时。(jdk1.6引入)

核心思路:通过CAS修改Mark Word值,修改失败自旋。

Synchronized底层实现原理_第2张图片

重量级锁: 等待线程自旋次数过多时、或超过两个线程线程抢锁时轻量级锁升级成重量级锁。使竞争线程阻塞并进入到锁池。优点:避免线程长时间自旋操作消耗cpu性能。缺点:线程间切换耗时。

锁只有升级没有降级。解锁

Synchronized底层实现原理_第3张图片

Monitor:

Synchronized底层实现原理_第4张图片
锁升级过程:
Synchronized底层实现原理_第5张图片
​ 综上,偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。

你可能感兴趣的:(高性能编程系列,多线程,java)