synchronized是Java中的关键字,它是Java中常用的锁,也是Java开发者最先接触的锁,下面就来简要谈一谈它(本文暂不涉及锁优化)
在并发编程中,一般要保证原子性、可见性、有序性,synchronized加持下的代码如何保证这三点:
public synchronized void func() { System.out.println("aaa"); }
public synchronized static void func() { System.out.println("bbb"); }
代码块中锁实例对象
public void func() { //修饰代码块,锁住this synchronized (this) { System.out.println("ccc"); } }
代码块中锁Class对象
public void func() { synchronized (Demo.class) { System.out.println("ddd"); } }
现在编写了一个Demo类,加入同步块和同步方法,代码如图
将这段代码编译,然后我们使用javap
反编译它的字节码,看一看加上关键字后底层究竟有什么不一样
代码块部分的反编译结果如图
从图中可以看出synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,编译器在同步代码块的开头和结尾的地方分别加上了monitorenter
和
monitorexit
;
当执行 monitorenter
指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。
Monitor 是基于 C++实现的,是JVM内置的,在Hotspot中由ObjectMonitor实现的。而且每个对象中都内置了一个 ObjectMonitor
对象。Monitor 依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的线程同步的。这种实现方式在阻塞和唤醒一个线程时要操作系统切换CPU状态来完成;所以这种状态转换需要耗费处理器时间。在一些情况下,状态转换消耗的时间有可能比用户代码执行的时间还要长。这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”,所以在JDK1.6中开发团队着重优化了synchronized 锁的效率(本文只讨论了重量级锁的情况)。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取而代之的是 ACC_SYNCHRONIZED
标识,这个标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,在调用的方法有此标志时,执行线程需要先获得monitor锁,然后开始执行方法,方法执行之后再释放monitor锁。所以也是使用的monitor锁;