详解Java中synchronized的实现原理

synchronized是Java原生的锁机制,可以实现线程对临界区的互斥访问。曾经synchronized因为性能低被称为重量级锁,但自从JDK 6对synchronized的各种优化之后,synchronized的性能和ReentrantLock的性能基本持平。synchronized相对于ReentrantLock的优势是其锁的释放由JVM确保,并且synchronized是Java语法层面的同步,更加清晰简单。

synchronized实现同步的基础是:Java中的每一个对象都可以作为锁,这个锁也被称为内部锁。线程在进入synchronized代码块前会先获取内部锁,内部锁是排他锁,这时其它线程就会阻塞挂起并等待锁的释放。synchronized可用于以下三种情况:

  • 对于普通同步方法,锁是当前实例对象
  • 对于类静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是synchronized括号里配置的对象

为什么说Java中的每一个对象都可以作为synchronized的锁呢?其实是synchronized用的锁信息存在Java对象头里。一个对象在堆内存中的存储布局分为三个部分:对象头、示例数据和对齐填充。其中,对象头由以下三个部分构成:

  • Mark Word,存储HashCode、GC分代年龄、锁信息
  • 指向类信息的指针
  • 数组长度(只有数组对象才有)

就在Mark Word里,存储着一个指向互斥量(重量级锁)的指针,这个互斥量称为Monitor,每个对象都有自己关联的Monitor。简单来说,synchronized实际上是基于对象的Monitor来实现互斥的。Monitor是JVM层面实现的,其具体内容我们不做深究,只简单说一下运作方式。当某对象被一个线程锁住时,该对象的对象头中的Mark Work会记录指向Monitor的地址,Monitor中会记录该线程的ID,Monitor中还有其它一些机制来实现线程之间的挂起和唤醒。

直接使用Monitor的方式是性能低下的,所以这种方式又被称为重量级锁。JDK1.6对synchronized进行了优化,synchronized一开始并不是直接就采用重量级锁,而是采用一种“锁升级”的方式,从偏向锁和轻量级锁逐步根据需求升级到重量级锁。为此,Mark Word采用了一种动态结构,可以在不同状态下代表不同的锁结构,如下图所示。


  • 偏向锁:经研究发现,大多数情况下,锁总是由同一线程多次获得。因此,当一个线程获取锁时,先进入偏向锁状态,当这个线程在此请求锁时,就无需再进行锁申请,直接获取锁即可,从而提高了性能。但当其它线程来获取锁时,偏向锁就失效了,然后升级到轻量级锁。
  • 轻量级锁:轻量级锁的理论依据是“大部分锁在同步周期内都不存在竞争”。因此轻量级锁是先用CAS手段来加锁,如果失败,说明存在竞争,则进入重量级锁。

参考文献

【1】《Java并发编程之美》翟陆续等著,电子工业出版社。

【2】《Java并发编程的艺术》方腾飞等著,机械工业出版社。

【3】《实战Java高并发程序设计(第2版)》葛一鸣著,电子工业出版社。


每日学习笔记,写于2020-04-19 星期日

你可能感兴趣的:(详解Java中synchronized的实现原理)