Synchronized 锁机制的实现原理
Synchronized是Java种用于进行同步的关键字,synchronized的底层使用的是锁机制实现的同步。在Java中的每一个对象都可以作为锁。
Java中synchronized的两个特性:
-
互斥性:即在同一时间内只允许同一个县城持有某一个对象锁,通过这种特性来实现多个线程中的协调机制,这样在同一时间内只有一个线程对同步的代码进行访问,互斥性往往也被称为原子性。
-
可见性:必须确保在获取锁的时候,线程内共享变量的值和主存一致,并且也必须保证在锁在被释放前,对共享变量所做的修改,对于随后获取锁的另一个线程是可见的(即在获取锁时应该获得的是最新的共享变量的值),否则另一个线程可能在本地缓存的某一个副本上继续操作从而导致结果不一致。
synchronized锁具体的三种形式:
- 对于普通同步方法,锁对象是当前实例对象,进入同步代码块前需要获得当前对象的锁。
- 对于静态同步方法,锁的是当前类的对象,在Java中每一个类都有一个Class对象。
- 对于同步方法块,锁的是synchronized圆括号内的对象,这里的对象可以是一个普通的对象,也可以是一个Class对象,如果是Class对象的话,也就是所谓的类锁了,而类锁是通过类的Class对象实现的。
synchronized 的原理
public class SynchronizedTest { public void readFile() throws IOException { synchronized(this) { System.out.println("同步代码块"); } } }
经过Javap 反编译后,
C:\data>javap -c SynchronizedTest.class Compiled from "SynchronizedTest.java" public class test.test.a.SynchronizedTest { public test.test.a.SynchronizedTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public void readFile() throws java.io.IOException; Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #24 // String 同步代码块 9: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 20 17: aload_1 18: monitorexit 19: athrow 20: return Exception table: from to target type 4 14 17 any 17 19 17 any }
可以看出synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
下面的是同步方法:
public class SynchronizedTest { public synchronized void readFile() throws IOException { System.out.println("同步代码块"); } }
反编译后如下图所示:

对于不同的对象头它们的总结如下表:
长度 | 内容 | 说明 |
---|---|---|
32/64bit | MarkWord | 存储对象的hashCode,GC分代和锁信息 |
32/64bit | Klass Point | 存储到类元数据的指针 |
32/64bit | Array Length | 这个只针对数组对象而言,存储数组的长度 |
Java对象头中的MarkWord里面的存储对象如下表:
锁状态 | 25bit | 4bit | 1bit是否偏向锁 | 2bit锁标志 |
---|---|---|---|---|
无锁状态 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
在运行期间,Mark Word里存储的数据会随着锁标志的变化而变化,它的变化如下表所示:

在64位的虚拟下,Mark Word是64bit的存储结构,其存储结构如下表:
锁状态 | 25bit | 31bit | 1bit | 4bit | 1bit | 2bit |
---|---|---|---|---|---|---|
cms_free | 分代年龄 | 偏向锁 | 锁标志位 | |||
无锁 | unused | hashCode | 0 | 01 | ||
偏向锁 | ThreadId(54bit) | Epoch(2bit) | 1 | 01 |
对象头的最后两位存储了锁的标志,01是初始状态表示无锁,其对象头里存储的是对象的哈希吗,随着锁级别的不同,对象头中存储的内容也会不同。偏向锁存储的当前占用此对象的线程ID;而轻量级锁则是存储指向线程栈中锁记录的指针。
Monitor监视器锁
其中轻量级锁和偏向锁是Java6对synchronized锁进行优化后增加的,我们稍后会进行分析。这里我们主要分析重量级锁,也就是通常所说的synchronized对象锁,锁标识为10,其中指针指向monitor对象(也称之为管程或者监视器锁)的起始地址。每个对象都存在一个monitor与之关联,对象与其monitor之间也存在着多种实现方式,如monitor可以与对象一起创建或者销毁或当前线程试图获取锁时自动生成,但一个monitor被某线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是有ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)
ObjectMonitor() { _header = NULL; _count = 0; //记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
ObjectMonitor 中有两个队列,_WaitSet 和 _EntryList ,用来保存ObjectWaiter 对象列表,每个等待锁的线程都会被封装成ObjectWaiter 对象,_owner 指向持有ObjectMonitor 对象的线程,当多个线程同时访问同一同步代码块或者同步方法时,首先会进入 _EntryList 队列,当线程获取到monitor 后进入_Owner 区域并把 monitor中的 _Owner 变量设置为当前线程,同时monitor 中的计数器count 加1,若线程调用wait() 方法,将释放当前持有的monitor,_owner变量恢复为null,count 自减 1 ,同时该线程进入_WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:

所以,monitor对象存在于每一个java对象的对象头(存储指针的指向),synchronized锁便是通过这种方式获取的,也是为什么java中任何对象都可以作为锁的原因,同时也是 notify/notifyAll/wait 方法等存在于顶级对象Object中的原因。
原文链接:https://www.jianshu.com/p/3eda3d375e7e