synchronized的实现原理
常见三种使用方法:
1)普通同步方法,锁是当前实例;
2)静态同步方法,锁是当前类的Class实例,Class数据存在永久代中,是该类的一个全局锁;
3)对于同步代码块,锁是synchronized括号里配置的对象。
Java中的每个对象都可以作为锁。当一个线程访问同步代码块时,需要首先获取锁,退出代码块或抛出异常时必须释放锁
“锁”到底是个什么东东?
首先通过源代码和反汇编代码研究锁的实现原理。
1)同步代码块源代码:
public class SynchronizedBlock {
public void method() {
synchronized (this) {
System.out.println("...");
}
System.out.println("...");
}
}
反汇编后的代码(javap -c):
public class SynchronizedBlock { // Method descriptor #6 ()V
// Stack: 1, Locals: 1
public SynchronizedBlock();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 return
Line numbers:
[pc: 0, line: 5]
Local variable table:
[pc: 0, pc: 5] local: this index: 0 type: SynchronizedBlock // Method descriptor #6 ()V
// Stack: 2, Locals: 2
public void method();
0 aload_0 [this]
1 dup
2 astore_1
3 monitorenter //在同步块开始位置插入monitorenter指令
4 getstatic java.lang.System.out : java.io.PrintStream [15]
7 ldc <String "..."> [21]
9 invokevirtual java.io.PrintStream.println(java.lang.String) : void [23]
12 aload_1
13 monitorexit //在同步块结束位置插入
14 goto 20
17 aload_1
18 monitorexit //在抛出异常位置释放锁
19 athrow //抛出异常指令
20 getstatic java.lang.System.out : java.io.PrintStream [15]
23 ldc <String "..."> [21]
25 invokevirtual java.io.PrintStream.println(java.lang.String) : void [23]
28 return
Exception Table:
[pc: 4, pc: 14] -> 17 when : any
[pc: 17, pc: 19] -> 17 when : any
Line numbers:
[pc: 0, line: 7]
[pc: 4, line: 8]
[pc: 12, line: 7]
[pc: 20, line: 10]
[pc: 28, line: 11]
Local variable table:
[pc: 0, pc: 29] local: this index: 0 type: SynchronizedBlock
Stack map table: number of frames 2
[pc: 17, full, stack: {java.lang.Throwable}, locals: {SynchronizedBlock, SynchronizedBlock}]
[pc: 20, chop 1 local(s)]
}
2)同步方法源代码:
public class SynchronizedMethod {
//普通同步方法
public synchronized void method1() {
System.out.println("...");
}
//静态同步方法
public synchronized static void method2() {
System.out.println("...");
}
}
反汇编后(javap -v):
public class SynchronizedMethod
SourceFile: "SynchronizedMethod.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // SynchronizedMethod
#2 = Utf8 SynchronizedMethod
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LSynchronizedMethod;
#14 = Utf8 method1
#15 = Fieldref #16.#18 // java/lang/System.out:Ljava/io/PrintStream;
#16 = Class #17 // java/lang/System
#17 = Utf8 java/lang/System
#18 = NameAndType #19:#20 // out:Ljava/io/PrintStream;
#19 = Utf8 out
#20 = Utf8 Ljava/io/PrintStream;
#21 = String #22 // ...
#22 = Utf8 ...
#23 = Methodref #24.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#24 = Class #25 // java/io/PrintStream
#25 = Utf8 java/io/PrintStream
#26 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
#29 = Utf8 method2
#30 = Utf8 SourceFile
#31 = Utf8 SynchronizedMethod.java
{
public SynchronizedMethod();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LSynchronizedMethod; public synchronized void method1();
flags: ACC_PUBLIC, ACC_SYNCHRONIZED //同步方法增加了ACC_SYNCHRONIZED标志
Code:
stack=2, locals=1, args_size=1
0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #21 // String ...
5: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LSynchronizedMethod; public static synchronized void method2();
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #21 // String ...
5: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8
LocalVariableTable:
Start Length Slot Name Signature
}
通过反汇编代码可以观察到:
同步代码块是使用MonitorEnter和MoniterExit指令实现的,在编译时,MonitorEnter指令被插入到同步代码块的开始位置,MoniterExit指令被插入到同步代码块的结束位置和异常位置。任何对象都有一个Monitor与之关联,当Monitor被持有后将处于锁定状态。MonitorEnter指令会尝试获取Monitor的持有权,即尝试获取锁。
同步方法依赖flags标志ACC_SYNCHRONIZED实现,字节码中没有具体的逻辑,可能需要查看JVM的底层实现(同步方法也可以通过Monitor指令实现)。ACC_SYNCHRONIZED标志表示方法为同步方法,如果为非静态方法(没有ACC_STATIC标志),使用调用该方法的对象作为锁对象;如果为静态方法(有ACC_STATIC标志),使用该方法所属的Class类在JVM的内部对象表示Klass作为锁对象。
下面是摘自《Java虚拟机规范》的话:
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。
同步一段指令集序列通常是由Java语言中的synchronized块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要编译器与Java虚拟机两者协作支持。
Java虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。无论是显式同步(有明确的monitorenter和monitorexit指令)还是隐式同步(依赖方法调用和返回指令实现的)都是如此。
编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须有执行其对应monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指令。
那Monitor到底是什么?一个作为锁的对象是什么样的工作机制?(Java对象头和Monitor)
Java对象头
对象头含有三部分:Mark Word(存储对象自身运行时数据)、Class Metadata Address(存储类元数据的指针)、Array length(数组长度,只有数组类型才有)。
重点在Mark Word部分,Mark Word数据结构被设计成非固定的,会随着对象的不同状态而变化,如下表所示。
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit锁标志位 | |
无锁状态 | hashCode | age | 0 | 01 | |
轻量级锁 | 执行栈中锁记录的指针 | 00 | |||
重量级锁 | 执行栈中锁记录的指针 | 10 | |||
GC标记 | 空 | 11 | |||
偏向锁 | 线程ID | Epoch | age | 1 | 01 |
、 锁的级别从低到高:无锁、偏向锁、轻量级锁、重量级锁。
Monitor
Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。
1)互斥:一个Monitor在一个时刻只能被一个线程持有,即Monitor中的所有方法都是互斥的。
2)signal机制:如果条件变量不满足,允许一个正在持有Monitor的线程暂时释放持有权,当条件变量满足时,当前线程可以唤醒正在等待该条件变量的线程,然后重新获取Monitor的持有权。
所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。
Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高。
JDK1.6对锁的优化
重量级锁
重量级锁基于Monitor实现,成本高。
轻量级锁
优化点:在没有多线程竞争的情况下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗。
什么情况下使用:关闭偏向锁或由于多线程竞争导致的偏向锁升级为轻量级锁。
获取锁步骤:
1)判断是否处于无锁状态,若是,则JVM在当前线程的栈帧中创建锁记录(Lock Record)空间,用于存放锁对象中的Mark Word的拷贝,官方称为Displaced Mark Word;否则执行步骤3)。
2)当前线程尝试利用CAS将锁对象的Mark Word更新为指向锁记录的指针。如果更新成功意味着获取到锁,将锁标志位置为00,执行同步代码块;如果更新失败,执行步骤3)。
3)判断锁对象的Mark Word是否指向当前线程的栈帧,若是说明当前线程已经获取了锁,执行同步代码,否则说明其他线程已经获取了该锁对象,执行步骤4)。
4)当前线程尝试使用自旋来获取锁,自旋期间会不断的执行步骤1),直到获取到锁或自旋结束。因为自旋锁会消耗CPU,所以不能无限的自旋。如果自旋期间获取到锁(其他线程释放锁),执行同步块;否则锁膨胀为重量级锁,当前线程阻塞,等待持有锁的线程释放锁时的唤醒。
释放锁步骤:
1)从当前线程的栈帧中取出Displaced Mark Word存储的锁记录的内容。
2)当前线程尝试使用CAS将锁记录内容更新到锁对象中的Mark Word中。如果更新成功,则释放锁成功,将锁标志位置为01无锁状态;否则,执行3)。
3)CAS更新失败,说明有其他线程尝试获取锁。需要释放锁并同时唤醒等待的线程。
偏向锁
优化点:在没有多线程竞争的情况下,减少轻量级锁的不必要的CAS操作。在无竞争情况下,完全消除同步。
优化方法:锁对象的Mark Word中记录获取锁的线程ID。
获取锁步骤:
1)判断锁对象是否是偏向锁(即锁标志位为01,偏向锁位为1),若为偏向锁状态执行2)。
2)判断锁对象的线程ID是否为当前线程的ID,如果是则说明已经获取到锁,执行代码块;否则执行3)。
3)当前线程使用CAS更新锁对象的线程ID为当前线程ID。如果成功,获取到锁;否则执行4)
4)当到达全局安全点,当前线程根据Mark Word中的线程ID通知持有锁的线程挂起,将锁对象Mark Word中的锁对象指针指向当前堆栈中最近的一个锁记录,偏向锁升级为轻量级锁,恢复被挂起的线程。
释放锁步骤:
偏向锁采用一种等到竞争出现时才释放锁的机制。当其他线程尝试竞争偏向锁时,当前线程才会释放释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点。
1)首先暂停持有偏向锁的线程。
2)撤销偏向锁,恢复到无锁状态或轻量级锁状态。
几种锁的对比:
优点 | 缺点 | 适用情况 | |
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步代码相差无几。 | 如果线程存在锁竞争,需要额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块的情况 |
轻量级锁 | 竞争的线程不会阻塞,提高了响应速度 | 长时间得不到锁的线程使用自旋消耗CPU | 追求响应速度。同步代码执行非常快 |
重量级锁 | 线程竞争不会使用自旋,不会消耗CPU | 线程出现竞争时会阻塞,响应速度慢 | 追求吞吐量。同步代码执行时间长 |
参考资料:
《Java并发编程的艺术》
(Java中的锁机制 synchronized )http://www.cnblogs.com/charlesblc/p/5994162.html
(synchronized实现原理)http://www.cnblogs.com/pureEve/p/6421273.html
(Java并行(2): Monitor)http://www.cnblogs.com/tomsheep/archive/2010/06/09/1754419.html
synchronized的实现原理的更多相关文章
- Java并发编程:Synchronized及其实现原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Synchronized及其实现原理
并发编程中synchronized一直是元老级角色,我们称之为重量级锁.主要用在三个地方: 1.修饰普通方法,锁是当前实例对象. 2.修饰类方法,锁是当前类的Class对象. 3.修饰代码块,锁是sy ...
- synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化
进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...
- synchronized的实现原理与应用
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. sync ...
- HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别
HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的 ...
- jdk1.8源码Synchronized及其实现原理
一.Synchronized的基本使用 关于Synchronized在JVM的原理(偏向锁,轻量级锁,重量级锁)可以参考 : http://www.cnblogs.com/dennyzhangdd/ ...
- 【死磕Java并发】-----深入分析synchronized的实现原理
记得刚刚開始学习Java的时候.一遇到多线程情况就是synchronized.相对于当时的我们来说synchronized是这么的奇妙而又强大,那个时候我们赋予它一个名字"同步". ...
- Java并发—–深入分析synchronized的实现原理
记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线 ...
- Synchronized之二:synchronized的实现原理
Java提供了synchronized关键字来支持内在锁.Synchronized关键字可以放在方法的前面.对象的前面.类的前面. 当线程调用同步方法时,它自动获得这个方法所在对象的内在锁,并且方法返 ...
- 【转】Java并发编程:Synchronized及其实现原理
一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步 ...
随机推荐
- 在windows和unbuntu上安装octave
windows安装octave 安装wiki Octave ftp库 从上述的库中可以找到对应的版本的octave的exe安装程序,或者是zip等的压缩包,建议直接下载对应系统的exe执行文件.安装. ...
- LAMP环境搭建Wordpress个人博客
LAMP简要介绍 L:LinuxA:Apache(httpd)M:MySQL , MariadbP:php, perl , python 静态资源:图片,文档,视频,HTML代码,CSS代码,js代码 ...
- centOS7设置静态ip后无法上网的解决,【亲可测】
最近在VMware虚拟机里玩Centos,装好后发现上不了网.经过一番艰辛的折腾,终于找到出解决问题的方法了.最终的效果是无论是ping内网IP还是ping外网ip,都能正常ping通.方法四步走: ...
- PictureBox使用异常
PictureBox的使用 代码: 显示部分 当我切换不同位置之间的照片时,出现这种问题: 但是当我代码改成下面的代码时,则错误消除 但我并不清楚原因
- ETL技术( Extract-Transform-Load) 数据仓库技术-比如kettle
每次面试,互联网的面试官,经常问我有没有用过ETL,每次我都懵逼,说没用过,觉得是多么高大上的东东,数据仓储 今天查了一下,我晕,自己天天用的Kettle就是最典型的ETL, 可以实现不同数据库之间的 ...
- hadoop和spark搭建记录
因玩票需要,使用三台搭建spark(192.168.1.10,192.168.1.11,192.168.1.12),又因spark构建在hadoop之上,那么就需要先搭建hadoop.历经一个两个下午 ...
- 【bzoj2656】[Zjoi2012]数列(sequence) 高精度
题目描述 给出数列 $A$ 的递推公式如下图所示,$T$ 次给定 $n$ ,求 $A_n$ . 输入 输入文件第一行有且只有一个正整数T,表示测试数据的组数.第2-T+1行,每行一个非负整数N. 输出 ...
- Python 模板 Jinja2
Python 模板 Jinja2 模板 要了解Jinja2,就需要先理解模板的概念.模板在Python的web开发中广泛使用,它能够有效的将业务逻辑和页面逻辑分开,使代码可读性更强.更加容易理解和维护 ...
- Ubuntu 18.04开发环境部署流程
部署流程 安装系统 安装Eclipse和jre 配置系统 安装辅助工具 安装系统 用安装盘安装即可. 一般boot 1G,swap按内存大小,home 20G,根剩余. 安装Eclipse和jre 解 ...
- CIR,CBS,EBS,PIR,PBS 名词解释 令牌桶应用
为了达到上述目的,我们需要对进入网络的流量进行监督,实现CAR(Committed Access Rate). CAR:将进入网络的用户流量的速率限制在约定的范围之内,从而避免引起网络拥塞. CIR( ...