前言
synchronized可以加载锁,那么是底层是如何实现的呢?
synchronized根据语法,作用在不同地方,底层实现不同。
1. synchronized锁代码块
代码,synchronized锁代码块:
public class Juc_LockOnThis {
public void decrStock(){
//synchronized锁代码块
synchronized (this){
System.out.println("hello world");
}
}
}
通过javap -v 查看字节码:
public void decrStock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: `monitorenter`
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String hello world
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: `monitorexit`
14: goto 22
17: astore_2
18: aload_1
19: `monitorexit`
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
从字节码中可知同步语句块的实现使用的是monitorenter
和monitorexit
指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取mark word里面存储的monitor,当 monitor的进入计数器为 0,那线程可以成功取得monitor,并将计数器值设置为1,取锁成功。
如果当前线程已经拥有 monitor 的持有权,那它可以重入这个 monitor ,重入时计数器的值也会加 1。倘若其他线程已经拥有monitor的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor并设置计数器值为0 ,其他线程将有机会持有 monitor 。
值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从上面的字节码中也可以看出有两个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。
可以简单理解为默认在代码中额外加了一个try {} finally{ exit} 语句,确保一定能执行到解锁的代码
2. Synchronized锁整个方法
同步方法的加锁、解锁是通过 Javac 编译器实现的,底层是借助ACC_SYNCHRONIZED访问标识符来实现的,代码如下所示:
public class Hello {
public synchronized void test() {
System.out.println("test");
}
}
方法级的同步是隐式,即无需通过字节码指令来控制
的,它实现在方法调用和返回操作之中。
JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED
访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有monitor,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。
下面我们看看字节码层面如何实现:
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, `ACC_SYNCHRONIZED` '重点再这里,方法层面的标志'
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/yg/edu/Test;
flags 是多位标志,ACC_PUBLIC表示方法是public的,而ACC_SYNCHRONIZED表示方法是同步的。