首先,每个对象都有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1.如果monitor的进入数为0,则线程进入monitor,将进入数设置为1,该线程为monitor的所有者;
2.如果线程已经占有monitor,只是重新进入,则进入数+1;
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获得所有权。
执行monitorexit的线程必须是objectref所对应的monitor的所有者,指令执行时,monitor的进入数-1,如果减为0,那么线程退出monitor,不再是持有者.
在将synchronized代码块反编译
javap -verbose ClassCompile
以后,我们可以发现调用了monitorenter和monitorexit指令实现同步. 如果synchronized同步代码块,底层是调用monitor的两个指令来实现锁。如果synchronized同步方法,底层是读取运行时常量池的ACC_SYNCHRONIZED标志来实现的.
The Java® Virtual Machine Specification:
Method-level synchronization is performed implicitly, as part of method invocation and return (§2.11.8). A synchronized
method is distinguished in the run-time constant pool‘s method_info
structure (§4.6) by the ACC_SYNCHRONIZED
flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED
is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized
method and the synchronized
method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized
method.
同步方法是隐式的。一个同步方法会在运行时常量池中的method_info结构体中存放ACC_SYNCHRONIZED标识符。当一个线程访问方法时,会去检查是否存在ACC_SYNCHRONIZED标识,如果存在,则先要获得对应的monitor锁,然后执行方法。当方法执行结束(不管是正常return还是抛出异常)都会释放对应的monitor锁。如果此时有其他线程也想要访问这个方法时,会因得不到monitor锁而阻塞。当同步方法中抛出异常且方法内没有捕获,则在向外抛出时会先释放已获得的monitor锁。
为什么ACC_SYNCHRONIZED在运行时常量池中,而看class文件是存放在access_flags中??
可以看到ACC_SYNCHRONIZED标识存放在常量池中,而method_info结构体中的access_flags字段是u2的,所以它只是一个指向常量池的标记。而常量池在加载时就会加载到运行时常量池中。
原文:https://www.cnblogs.com/fhourn/p/11313514.html