文章目录
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虚拟机中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。
对象头:主要包括两部分:Mark Word、类型指针(Class Metadata Address);当对象为数组对象时,有一个Array Length。
Mark Word:本质上是堆当中的一段内存区域,32/64位机器的长度分别时32/64位。32为图解如下:
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值,修改失败自旋。
重量级锁: 等待线程自旋次数过多时、或超过两个线程线程抢锁时轻量级锁升级成重量级锁。使竞争线程阻塞并进入到锁池。优点:避免线程长时间自旋操作消耗cpu性能。缺点:线程间切换耗时。
锁只有升级没有降级。解锁
Monitor:
锁升级过程:
综上,偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。