Synchronized关键字
Synchronized 简介
在某些多线程场景下,如果不进行同步会导致数据不安全,为了解决线程安全的问题,引入了锁的概念。
java中常用的锁有synchronized和lock两种,本文来分析synchronized关键字的原理。
作用:
保证被Synchronized修饰的方法或代码同一时刻最多只有1个线程执行。
Synchronized使用场景:
- Synchronized修饰普通同步方法:锁对象当前实例对象
- Synchronized修饰静态同步方法:锁对象是当前的类Class对象
- Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象(可以是某个对象,也可以是某个类)
Synchronized实现原理
Java对象头
java对象由如下几部分组成:
- 对象头
- Mark Word:包含一系列的标记位比如hashcode、GC分代年龄、偏向锁位,锁标志位等,而且在对象被加了不同量级的锁时所包含的内容和布局都有所不同
- Klass Pointer:是一个指针,指向描述这个对象类型的元对象
- 实例数据
- 描述成员变量的信息,如果成员变量是引用类型,那么它就是一个指针
- 实例数据的大小是所有成员变量的占用空间(基本数据类型大小+指针大小)
- 对齐填充:java中会将对象大小设定为8bytes的整数倍,如果对象头+实例数据的大小不是8bytes的倍数,那么会在padding区域填充字节使得对象占用空间是8bytes的倍数
Mark Word(对象头)
Synchronized在JVM中的实现原理
Synchronized修饰代码块
Synchronized代码块同步在需要同步的代码块开始的位置插入monitorentry指令,在同步结束的位置或者异常出现的位置插入monitorexit指令。
JVM要保证monitorentry和monitorexit都是成对出现的,任何对象都有一个monitor与之对应,当这个对象的monitor被持有以后,它将处于锁定状态。
同步代码块如下:
public class SyncCodeBlock {
public int i;
public void syncTask(){
synchronized (this){
i++;
}
}
}
对同步代码块编译后的class字节码文件反编:
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此处,进入同步方法
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此处,退出同步方法
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此处,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字节码.......
同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句。
为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行monitorexit语句,因此会出现两句monitorexit语句。
Synchronized修饰方法
Synchronized方法同步是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。
同步方法代码如下:
public class SyncMethod {
public int i;
public synchronized void syncTask(){
i++;
}
}
对同步方法编译后的class字节码反编译:
public synchronized void syncTask();
descriptor: ()V
//方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
}
方法开始和结束的地方都没有出现monitorentry和monitorexit指令,但是出现的ACC_SYNCHRONIZED标志位。