Synchronized 是常被我们用来保证临界区以及临界资源安全的解决方案。它可以保证当有多个线程访问同一段代码,操作共享数据时,其他线程必须等待正在操作线程完成数据处理后再进行访问。即 Synchronized 可以达到线程互斥访问的目的。
所以,我们可以了解到,Synchronized锁代表的锁机制有如下两种特性:互斥型和可见性。
-
互斥性:同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中并发安全;
-
可见性:确保锁在释放之前所做的操作,对之后的其他线程是可见的(即之后获取到该锁的线程获取到的共享变量是最新的)。
除此之外,JDK 1.6后还对synchronized锁进行了优化,使其摆脱了重量级锁的称号。接下来就来了解以下synchronized的实现以及优化。
一、Synchronized对应的锁对象
理论上Java中所有的对象都可以作为锁,Java中根据synchronized使用的场景不同,其锁对象也是不一样的。可以有以下场景:
场景 | 具体分类 | 锁对象 | 代码示例 |
---|---|---|---|
修饰方法 | 实例方法 | 当前实例对象 | public synchronized void method () {
... } |
... | 静态方法 | 当前类的Class对象 | public static synchronized void method () {
... } |
修饰代码块 | 代码块 | ( ) 中配置的对象 |
synchronized(object) {
... } |
所以,当一个线程要访问一段同步代码块时,它必须获取到如上表中的锁对象。那么这一过程在字节码中又是怎么表示的呢?
二、 Monitor机制与Java对象头
首先我们来看一段小Demo:
Copy
public class Demo {
public static void main(String[] args) {
synchronized (Demo.class) { }
method();
}
private static void method() { }
}
可以看到执行同步代码块首先需要去执行monitorenter
指令,退出的时候需要执行monitorexit
指令。我们来观察monitorenter
指令底层的逻辑,其源码如下:
Copy
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be N