- 1、synchronized的三种应用方式
- 1.1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰实例方法案例
- 反编译结果分析(ACC_SYNCHRONIZED)
- 1.2. 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰静态方法案例
- 反编译结果分析(ACC_SYNCHRONIZED)
- 1.3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
- 修饰代码块案例
- 反编译结果分析(monitorentermonitorexit)
- monitorenter
- monitorexit
- 获取monitor的过程
- 1.4 三种方式说明
- 什么是monitor?
- 参考链接
public synchronized void print1() {
System.out.println("Hello World");
}
反编译结果分析(ACC_SYNCHRONIZED)
public synchronized void print1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String Hello World
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8
说明:当方法被调用时,会检查ACC_SYNCHRONIZED标志是否被设置,若被设置,线程会先获取monitor,获取成功才能执行方法体,方法执行完成后会再次释放monitor。在方法执行期间,其他线程都无法获得同一个monitor对象。
1.2. 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 修饰静态方法案例public static synchronized void print2() {
System.out.println("Hello World");
}
反编译结果分析(ACC_SYNCHRONIZED)
public static synchronized void print2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String Hello World
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8
}
说明:当方法被调用时,会检查ACC_SYNCHRONIZED标志是否被设置,若被设置,线程会先获取monitor,获取成功才能执行方法体,方法执行完成后会再次释放monitor。在方法执行期间,其他线程都无法获得同一个monitor对象。
1.3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 修饰代码块案例public class SynchTestDemo {
public void print() {
synchronized ("得物") {
System.out.println("Hello World");
}
}
}
反编译结果分析(monitorentermonitorexit)
public void print();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // String 得物
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Hello World
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
synchronized关键字被编译成字节码后会被翻译成monitorenter和monitorexit两条指令分别在同步块逻辑代码的起始位置与结束位置
monitorenter任何一个对象都有一个monitor与其相关联,当且有一个monitor被持有后,它将处于锁定的状态,其他线程无法来获取该monitor。当JVM执行某个线程的某个方法内部的monitorenter时,他会尝试去获取当前对应的monitor的所有权
monitorexit能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程;
获取monitor的过程1.4 三种方式说明如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权;
- synchronized修饰的实例方法和静态方式是通过 ACC_SYNCHRONIZED 标示符来实现加锁的。
- synchronized修饰的代码块是通过指令monitorenter和monitorexit来实现的。
- 上述同步方式从本质上看是没有区别的,两个指令的执行都是JVM调用操作系统的互斥原语mutex来实现的,被阻塞的线程会被挂起、等待重新调度,会导致线程在“用户态”和“内核态”进行切换,就会对性能有很大的影响。
monitor通常被描述为一个对象,可以将其理解为一个同步工具,或者可以理解为一种同步机制。所有的Java对象自打new出来的时候就自带了一把锁,就是monitor锁,也就是对象锁,存在于对象头(Mark Word),锁标识位为10,指针指向的是monitor对象起始地址。
参考链接1)https://baijiahao.baidu.com/s?id=1713003848756478129&wfr=spider&for=pc&searchword=synchronized%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86
2)https://www.cnblogs.com/wuzhenzhao/p/10250801.html



