synchronized实现原理
2020-05-25 本文已影响0人
cbhe
synchronize可以修饰方法、代码块
当修饰代码块的时候其原理就是在编译时在代码块对应的字节码的上下分别增加monitorenter和moniterexit字节码指令。其中monitorenter指令是占用操作数栈顶引用对象,monitorexit指令是解除占用。如果修饰方法时,方法在编译成字节码时会被标记为ACC_SYNCHRONIZED,其实就是相当于在方法全部代码的前后分别增加moniterenter和moniterexit。
官方对monitorenter和monitorexit的解释如下:
monitorenter
每个对象都与一个monitor关联。当前仅当一个monitor被一个所有者所拥有时,这个monitor就被锁定了。线程执行monitorenter字节码时就是在尝试获取当前操作数栈顶引用的对象锁对应的monitor的所有权。分为如下几种情况:
- 如果monitor的拥有者是0个,则当前执行线程获得了monitor的所有权,并将拥有者计数器值设为1。
- 如果当前线程已经是该monitor的拥有者,则将拥有者计数器加一,当前线程继续执行代码,无需阻塞。
- 如果该monitor被其他线程所拥有,则当前线程阻塞,直到monitor的拥有者计数器为0时才可获取到该monitor的所有权并开始执行后续代码。
monitorexit的解释跟monitorenter正好是相反的。
下面我们来看一下一段synchronized代码的字节码到底是什么样的:
代码如下:
public class Test{
public void sayHello(){
Object object = new Object();
synchronized (object){
System.out.println("hello");
}
}
}
字节码如下:
public void sayHello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String hello
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit
22: goto 30
25: astore_3
26: aload_2
27: monitorexit
28: aload_3
29: athrow
30: return
可以看到偏移量为11和21的两行代码正是我们刚说过的。偏移量0-10所做的事情是new 了一个Object对象并初始化,将其引用放在了操作数栈顶。11其实就是在请求这个object的monitor的所有权。