Java synchronized底层实现原理以及锁优化

目录
  • 一、概述
    • synchronized简介
    • synchronized作用
    • synchronized的使用
  • 二、实现原理
  • 三、理解Java对象头
  • 四、JVM对synchronized的锁优化
    • 1、偏向锁
    • 2、轻量级锁
    • 3、重量级锁
    • 4、自旋锁
    • 5、锁消除
    • 6、锁粗化
  • 总结

一、概述

synchronized简介

在多线程并发编程中 synchronized 一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着 Java SE 1.6 对synchronized 进行了各种优化之后,有些情况下它就并不那么重,Java SE 1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。这块在后续介绍中会慢慢引入。

synchronized的基本语法

synchronized 有三种方式来加锁,分别是

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized作用

  • 原子性:synchronized保证语句块内操作是原子的
  • 可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
  • 有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

synchronized的使用

  • 修饰实例方法,对当前实例对象加锁
  • 修饰静态方法,多当前类的Class对象加锁
  • 修饰代码块,对synchronized括号内的对象加锁

二、实现原理

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

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

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

这里要注意:

  • synchronized是可重入的,所以不会自己把,自己锁死
  • synchronized锁一旦被一个线程持有,其他试图获取该锁的线程将被阻塞。

关于ACC_SYNCHRONIZED 、monitorenter、monitorexit指令,可以看一下下面的反编译代码:

public class SynchronizedDemo {
    public synchronized void f(){    //这个是同步方法
        System.out.println("Hello world");
    }
    public void g(){
        synchronized (this){		//这个是同步代码块
            System.out.println("Hello world");
        }
    }
    public static void main(String[] args) {

    }
}

使用javap -verbose SynchronizedDemo反编译后得到

我们看到对于同步方法,反编译后得到ACC_SYNCHRONIZED 标志,对于同步代码块反编译后得到monitorenter和monitorexit指令。

三、理解Java对象头

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

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

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

HotSpot虚拟机的对象头分为两部分信息,第一部分用于存储对象自身运行时数据,如哈希码、GC分代年龄等,这部分数据的长度在32位和64位的虚拟机中分别为32位和64位。官方称为Mark Word。另一部分用于存储指向对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分存储数组长度。

虚拟机位数 对象头结构 描述
32位/64位 Mark Word 存储对象的哈希码、GC分代年龄、锁信息等
32位/64位 Class MetaData Address 指向对象类型数据的指针
32位/64位 数组长度 如果是数组对象的话,有这一部分,否则没有

由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间。

四、JVM对synchronized的锁优化

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

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

1、偏向锁

偏向锁是JDK1.6中引用的优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能。

偏向锁的获取:

  • 判断是否为可偏向状态
  • 如果为可偏向状态,则判断线程ID是否是当前线程,如果是进入同步块;
  • 如果线程ID并未指向当前线程,利用CAS操作竞争锁,如果竞争成功,将Mark Word中线程ID更新为当前线程ID,进入同步块
  • 如果竞争失败,等待全局安全点,准备撤销偏向锁,根据线程是否处于活动状态,决定是转换为无锁状态还是升级为轻量级锁。

当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设置为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word中,如果CAS操作成功。持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。

偏向锁的释放:

偏向锁使用了遇到竞争才释放锁的机制。偏向锁的撤销需要等待全局安全点,然后它会首先暂停拥有偏向锁的线程,然后判断线程是否还活着,如果线程还活着,则升级为轻量级锁,否则,将锁设置为无锁状态。

2、轻量级锁

轻量级锁也是在JDK1.6中引入的新型锁机制。它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

加锁过程:

在代码进入同步块的时候,如果此对象没有被锁定(锁标志位为“01”状态),虚拟机首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储对象目前Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word)。然后虚拟机使用CAS操作尝试将对象的Mark Word更新为指向锁记录(Lock Record)的指针。如果更新成功,那么这个线程就拥有了该对象的锁,并且对象的Mark Word标志位转变为“00”,即表示此对象处于轻量级锁定状态;如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块中执行,否则说明这个锁对象已经被其他线程占有了。如果有两条以上的线程竞争同一个锁,那轻量级锁不再有效,要膨胀为重量级锁,锁标志变为“10”,Mark Word中存储的就是指向重量级锁的指针,而后面等待的线程也要进入阻塞状态。

解锁过程:

如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作将对象当前的Mark Word与线程栈帧中的Displaced Mark Word交换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统重量级锁开销更大。

3、重量级锁

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

4、自旋锁

互斥同步对性能影响最大的是阻塞的实现,挂起线程和恢复线程的操作都需要转入到内核态中完成,这些操作给系统的并发性能带来很大的压力。
于是在阻塞之前,我们让线程执行一个忙循环(自旋),看看持有锁的线程是否释放锁,如果很快释放锁,则没有必要进行阻塞。

5、锁消除

锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是检测到不可能发生数据竞争的锁进行消除。

6、锁粗化

如果虚拟机检测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。

参考:深入理解Java并发之synchronized实现原理
参考:Java的对象头和对象组成详解

总结

到此这篇关于Java synchronized底层实现原理以及锁优化的文章就介绍到这了,更多相关synchronized底层实现原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java同步关键字synchronize底层实现原理解析

    目录 1 字节码层实现 1.1 InterpreterRuntime::monitorenter 1.1.1 函数参数 JavaThread *thread 1.1.2 函数体 2 偏向锁 2.1 偏向锁的意义 2.2 偏向锁的获取 2.2.1 markOop mark = obj->mark() 2.2.2 判断mark是否为可偏向状态 2.2.3 判断mark中JavaThread的状态 2.2.4 通过CAS原子指令 2.2.5 如果执行CAS失败 2.3 偏向锁的撤销 2.4 轻量级锁

  • Java synchronize底层实现原理及优化

    首先来说下synchronize和Lock的区别: 两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别: synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代码不执行,所以Lock一般在Finally中释放,而synchronize释放锁是由JVM自动执行的. Lock有共享锁的概念,所以可以设置读写锁提高效率,synchronize不能.(两者都可重入) Lock可以让线程在获取锁的过程中响应中断

  • Java synchronized底层实现原理以及锁优化

    目录 一.概述 synchronized简介 synchronized作用 synchronized的使用 二.实现原理 三.理解Java对象头 四.JVM对synchronized的锁优化 1.偏向锁 2.轻量级锁 3.重量级锁 4.自旋锁 5.锁消除 6.锁粗化 总结 一.概述 synchronized简介 在多线程并发编程中 synchronized 一直是元老级角色,很多人都会称呼它为重量级锁.但是,随着 Java SE 1.6 对synchronized 进行了各种优化之后,有些情况下

  • Java synchronized底层的实现原理

    目录 监视器 底层实现 执行流程 总结 前言: 想了解 synchronized 是如何运行的?就要先搞清楚 synchronized 是如何实现? synchronized 同步锁是通过 JVM 内置的 Monitor 监视器实现的,而监视器又是依赖操作系统的互斥锁 Mutex 实现的,那接下来我们先来了解一下监视器. 监视器 监视器是一个概念或者说是一个机制,它用来保障在任何时候,只有一个线程能够执行指定区域的代码. 一个监视器像是一个建筑,建筑里有一个特殊的房间,这个房间同一时刻只能被一个

  • Java CAS底层实现原理实例详解

    这篇文章主要介绍了Java CAS底层实现原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.CAS(compareAndSwap)的概念 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制. CAS(V, A, B),V为内存地址.A为预期原值,B为新值.如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值.否则,说明已经被其他线程更新,处理器不做任何操作:无论哪种情

  • 详解Java Synchronized的实现原理

    目录 Synchronized Synchronized的使用方式 Synchronized的底层实现 1.Java对象头 2.Monitor 3.线程状态流转在Monitor上体现 Synchronized 的锁升级 谈到多线程就不得不谈到Synchronized,重要性不言而喻,今天主要谈谈Synchronized的实现原理. Synchronized synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为”同步锁“.

  • Java LinkedHashMap 底层实现原理分析

    在实现上,LinkedHashMap很多方法直接继承自HashMap,仅为维护双向链表覆写了部分方法.所以,要看懂 LinkedHashMap 的源码,需要先看懂 HashMap 的源码. 默认情况下,LinkedHashMap的迭代顺序是按照插入节点的顺序.也可以通过改变accessOrder参数的值,使得其遍历顺序按照访问顺序输出. 这里我们只讨论LinkedHashMap和HashMap的不同之处,LinkedHashMap的其他操作和特性具体请参考HashMap 我们先来看下两者的区别:

  • synchronized底层实现原理

    测试类: public class SynchronizedTest {     public void get() {         synchronized (this) {             System.out.println("小张你好鸭!");         }     } } 字节码文件(怎么看?? idea => view => Show ByteCode) 再来说原理: 基于对象的监视器(ObjectMonitor),我们在字节码文件里面可以看到

  • Java并发底层实现原理学习心得

    我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为汇编语言,然后转为操作系统指令,然后转为1,0,最后CPU进行识别执行. 提到java的并发,我们不由的就会想到java中常见的键字:volatile和synchronized,我们接下来就会从这两个关机字展开分析: volatile的底层实现原理 synchronized的实现原理和应用 volatile 说到volatile,在java

  • java synchronized 锁机制原理详解

    目录 前言: 1.synchronized 的作用: 2.synchronized 底层语义原理: 3. synchronized 的显式同步与隐式同步: 3.1.synchronized 代码块底层原理: 3.2.synchronized 方法底层原理: 4.JVM 对 synchronized 锁的优化: 4.1.锁升级:偏向锁->轻量级锁->自旋锁->重量级锁 4.1.1.synchronized 的 Mark word 标志位: 4.1.2.锁升级过程: 4.2.锁消除: 4.3

  • Java synchronized轻量级锁的核心原理详解

    目录 1.轻量级锁的原理 2.轻量级锁的分类 1.普通自旋锁 2.自适应自旋锁 3.轻量级锁的膨胀 总结 问题: 什么是自旋锁? 说一下 synchronized 底层实现原理? 多线程中 synchronized 锁升级的原理是什么? 1. 轻量级锁的原理 引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗.重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗. 轻量级锁的使用

随机推荐