synchonized实现有两种方式,一种是直接加在方法上,对整个方法进行加锁。另一种是对方法内的某一个代码块进行加锁,那么这两种加锁方式在虚拟机内的实现是怎样的呢。
对方法直接进行加锁:方法级的同步时隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之间。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要先成功持有管程,然后才能执行方法,最后当方法完成时释放管程(无论是否正常完成都会释放)。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个方法所持有的管程将在异常抛到同步方法之外时自动释放。
对方法内部的某一个代码块进行加锁:java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要javac 编译器和java虚拟机两者共同协作支持。在同步代码块执行开始的时候,将会调用monitorenter指令,当代码块结束时将调用monitorexit指令,编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有异常,它的目的就是用来执行monitorexit指令。