0. 前言
synchronized关键字作为Java常用的同步锁机制,是最简单的解决并发问题的一种方法,它具备以下特点:
1.原子性:确保线程互斥的访问同步代码;
2.可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
3.有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;
1. Synchronized 的使用
从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中,锁有个专门的名字:对象监视器(Object Monitor)。
Synchronized总共有三种用法:
1.当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
2.当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
3.当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;
测试代码:
public class SynchronizesTest {
static int w = 1;
public static void main(String[] arg0){
synchronized (SynchronizesTest.class){
w = 2;
}
}
public synchronized void syncMethod(){
w = 3;
}
private void syncThisMethod(){
w = 4;
synchronized (this){
w = 5;
}
}
int syncObjectMethod(){
int i = 6;
synchronized (new Object()){
i = 7;
}
return 0;
}
}
- 反编译上面的类:
javap -v SynchronizesTest.class
E:\workspace\...\app\src\main\java\com\linx\>javap -v SynchronizesTest.class Classfile /E:/workspace/.../app/src/main/java/com/linx/SynchronizesTest.class Last modified 2019-5-17; size 872 bytes MD5 checksum 852f9911b0a44e910f2c0b2c14dc0440 Compiled from "SynchronizesTest.java" public class com.linx.SynchronizesTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: ... { static int w; descriptor: I flags: ACC_STATIC public com.linx.SynchronizesTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // class com/linx/SynchronizesTest 2: dup 3: astore_1 4: monitorenter 5: iconst_2 6: putstatic #3 // Field w:I 9: aload_1 10: monitorexit 11: goto 19 14: astore_2 15: aload_1 16: monitorexit 17: aload_2 18: athrow 19: return Exception table: from to target type 5 11 14 any 14 17 14 any LineNumberTable: line 6: 0 line 7: 5 line 8: 9 line 9: 19 StackMapTable: ... ... public synchronized void syncMethod(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=1, locals=1, args_size=1 0: iconst_3 1: putstatic #3 // Field w:I 4: return LineNumberTable: line 12: 0 line 13: 4 int syncObjectMethod(); descriptor: ()I flags: Code: stack=2, locals=4, args_size=1 0: bipush 6 2: istore_1 3: new #4 // class java/lang/Object 6: dup 7: invokespecial #1 // Method java/lang/Object."<init>":()V 10: dup 11: astore_2 12: monitorenter 13: bipush 7 15: istore_1 16: aload_2 17: monitorexit 18: goto 26 21: astore_3 22: aload_2 23: monitorexit 24: aload_3 25: athrow 26: iconst_0 27: ireturn Exception table: from to target type 13 18 21 any 21 24 21 any ... } SourceFile: "SynchronizesTest.java"
- monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1.如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权; - monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;
- ACC_SYNCHRONIZED:作为同步方法的标志。作用原理也是monitor
当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
2. 监视器(Monitor)
任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。 Synchronized在JVM里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。
1.MonitorEnter指令:插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象Monitor的所有权,即尝试获得该对象的锁;
2.MonitorExit指令:插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit;
那什么是Monitor?可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。
一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。
也就是说Synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。
3. 总结
- synchronized作为常用的同步锁,它与其他的锁机制相同,都是通过CAS竞争获取锁对象
- synchronized方法,monitor是这个对象本身,仅仅同步引用这个对象的线程
- synchronized(object),monitor是参数对象
- synchronised(Clazz.class),monitor是方法区中这个类的模板Class,锁具备唯一性
- 注意:一般要求所具备唯一性,所以monitor一般是静态常量;
- 更多的了解:自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、锁粗化、锁消除等,大家自行学习。高并发场景对同步锁要求很高,效率与安全怎么平衡…