title: synchronized原理详解
date: 2019-11-27
author: qinghaihu
categories:
- 并发编程
tags:- synchronized
synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。
加锁方式
- 同步实例方法,锁的是当前实例对象
- 同步类方法,锁的是当前类对象(类加载后的Class对象)
- 同步代码块,锁的是括号里面的对象
synchronized底层原理
synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock)、锁消除(Lock Elimination)、轻量级锁(Lightweight)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。
synchronized关键字被编译成字节码后会被翻译成monitorenter 和monitorexit两条指令分别在同步块逻辑代码的起始位置与结束位置。
Monitor锁.png这里以线程安全的单例设计模式为例,利用javap -verbose Singleton.class命令,我们看看编译后的部分汇编代码。
public class Singleton {
/**
* 查看汇编指令
* -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp
*/
private volatile static Singleton myinstance;
public static Singleton getInstance() {
if (myinstance == null) {
synchronized (Singleton.class) {
if (myinstance == null) {
myinstance = new Singleton();//对象创建过程,本质可以分文三步
//对象延迟初始化
}
}
}
return myinstance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
public static com.it.edu.jmm.Singleton getInstance();
descriptor: ()Lcom/it/edu/jmm/Singleton;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: getstatic #2 // Field myinstance:Lcom/it/edu/jmm/Singleton;
3: ifnonnull 37
6: ldc #3 // class com/it/edu/jmm/Singleton
8: dup
9: astore_0
10: monitorenter
11: getstatic #2 // Field myinstance:Lcom/it/edu/jmm/Singleton;
14: ifnonnull 27
17: new #3 // class com/it/edu/jmm/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field myinstance:Lcom/it/edu/jmm/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #2 // Field myinstance:Lcom/it/edu/jmm/Singleton;
40: areturn
Exception table:
from to target type
11 29 32 any
32 35 32 any
每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:
Monitor锁机制.png文章开头有提到,synchronized锁是一种对象锁,锁的是对象,那对象是如何记录的锁信息呢?这就不得不给提到对象的内存布局。
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头)、实例数(Instance Data)和对齐填充(Padding)。
对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等。
实例数据:即创建对象时,对象中成员变量,方法等
对齐填充:对象的大小必须是8字节的整数倍
网友评论