同步代码块/同步方法是通过synchronized关键字提供的java语言层面的线程同步工具,在编译成字节码文件后,生成对应的jvm字节码指令或标记。上一篇博客中介绍了对象锁相关知识,其中提到锁最终可能会升级为重量级锁,即对象头mark word中锁标记为10,此时,mark word中其他位指向了一个互斥量,它是一个锁对象的监视器对象,这篇文章主要介绍synchronized和监视器对象。
synchronized
是一个jdk提供的可重入的,非公平的内置锁,可以分为 同步块
, 普通同步方法
, 静态同步方法
。
先从一个没有同步的普通对象开始
public class SynchronizedTest { private static int count; public int getAndIncrement() { return count++; } } 复制代码
观察字节指令:
可以看到getAndIncrement方法就是1.加载count的值,2.与1相加,3.写回。
同步代码块
public class SynchronizedTest { private static int count; private Object lock = new Object(); public int getAndIncrement() { synchronized (lock) { return count++; } } } 复制代码
使用代码同步块,锁对象为lock,字节码指令如下:
可以看到加了synchronized同步块之后,字节码里插入了1个monitorenter,2个monitorexit指令,而且原来的代码异常会被捕获,这样在异常时都能转到异常处理指令(19行)处处理,最终退出监视器对象。
同步方法(非静态)
public class SynchronizedTest { private static int count; public synchronized int getAndIncrement() { return count++; } } 复制代码
字节码指令:
可以看到同步方法,并没有在字节码中插入monitorenter,monitorexit指令,它有一个 ACC_SYNCHRONIZED 标志,在执行方法调用时,如果有此标志,则首先要获取monitor,跟同步代码块同理,此时的锁对象为方法所属对象(this)。
同步方法(静态)
public class SynchronizedTest { private static int count; public static synchronized int getAndIncrement() { return count++; } } 复制代码
字节码:
静态同步方法跟普通同步方法基本相同,不同点在于它的锁对象是 Class<SynchronizedTest>
对象。
监视器(或管程)是对多线程并发访问共享内存进行管理的一种方式,它封装了多线程的两个核心概念:互斥与同步(协作/通信),一个monitor对象封装了enter/exit/condition variable/wait/signal等方法和变量。
上图是MESA管程模型,可以看到线程在访问之前都要先在Entry队列排队
java中monitor与上面模型相似,但实现略有不同,每一个java对象都有一个monitor与之关联,它是实现线程同步的关键。
java monitor 机制
可以看到,java monitor同样有入口队列,等待队列,但等待队列的线程可以直接去获取锁而不需要先进入入口队列,所以synchronized是一个非公平锁实现。
再来看一下hotspot的monitor实现 objectMonitor
:
任意时刻,只有一个线程处于活动状态(持有监视器),所以获取到监视器就是获取到了锁,此时,锁对象对象头mark word锁标志变成10,并指向了该监视器对象,释放了监视器就释放了锁。线程的同步(wait/notify/notifyAll)通过monitor来实现,这也是为什么wait/notify/notifyAll要放在synchronized作用范围内。
关于中断,synchronized中,等待线程是无法响应中断的(Thread.interupt())。