1、反汇编方式理解synchronized原理
(1)源码
public class Test { private static Object obj = new Object(); public static void main(String[] args) { synchronized (obj) { System.out.println("1"); } } public synchronized void test() { System.out.println("a"); } }
(2)反汇编查看字节码指令
在monitorenter和monitorexit之间执行的是代码逻辑
(3)monitorenter
每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:
synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量:owner拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待
(4)monitorexit
能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个
2、查看JVM源码
(1)下载源代码(因为synchronized的源代码是c++写的)
选择版本:
选择格式:
(2)查看源代码
在HotSpot虚拟机中,monitor是由ObjectMonitor实现的。其源码是用c++来实现的,位于HotSpot虚拟机源码ObjectMonitor.hpp文件中(src/share/vm/runtime/objectMonitor.hpp)。ObjectMonitor主要数据结构如下:
ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; // 线程的重入次数 _object = NULL; // 存储该monitor的对象 _owner = NULL; // 标识拥有该monitor的线程 _WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL; _succ = NULL; _cxq = NULL; // 多线程竞争锁时的单向列表 FreeNext = NULL; _EntryList = NULL; // 处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0; _SpinClock = 0; OwnerIsThread = 0; }
3、monitor竞争
执行monitorenter时,会调用InterpreterRuntime.cpp(位于:src/share/vm/interpreter/interpreterRuntime.cpp) 的 InterpreterRuntime::monitorenter函数。具体代码可参见HotSpot源码
if (UseBiasedLocking) {//是否用偏向锁 // Retry fast entry if bias is revoked to avoid unnecessary inflation ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK); } else { ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); } assert(Universe::heap()->is_in_reserved_or_null(elem->obj()), "must be NULL or an object");
对于重量级锁,monitorenter函数中会调用 ObjectSynchronizer::slow_enter最终调用 ObjectMonitor::enter(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下:
void ATTR ObjectMonitor::enter(TRAPS) { // The following code is ordered to check the most common cases first // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors. Thread * const Self = THREAD ; void * cur ; // 通过CAS操作尝试把monitor的_owner字段设置为当前线程 cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; if (cur == NULL) { // Either ASSERT _recursions == 0 or explicitly set _recursions = 0. assert (_recursions == 0 , "invariant") ; assert (_owner == Self, "invariant") ; // CONSIDER: set or assert OwnerIsThread == 1 return ; } // 线程重入,recursions++ if (cur == Self) { // TODO-FIXME: check for integer overflow! BUGID 6557169. _recursions ++ ; return ; } // 如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程 if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error"); _recursions = 1 ; // Commute owner from a thread-specific on-stack BasicLockObject address to // a full-fledged "Thread *". _owner = Self ; OwnerIsThread = 1 ; return ; } // 省略一些代码 for (;;) { jt->set_suspend_equivalent(); // cleared by handle_special_suspend_equivalent_condition() // or java_suspend_self() // 如果获取锁失败,则等待锁的释放; EnterI (THREAD) ; if (!ExitSuspendEquivalent(jt)) break ; // // We have acquired the contended monitor, but while we were // waiting another thread suspended us. We don‘t want to enter // the monitor while suspended because that would surprise the // thread that suspended us. // _recursions = 0 ; _succ = NULL ; exit (false, Self) ; jt->java_suspend_self(); } Self->set_current_pending_monitor(NULL); }
4、monitor等待
竞争失败等待调用的是ObjectMonitor对象的EnterI方法(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下所示:
void ATTR ObjectMonitor::EnterI (TRAPS) { Thread * Self = THREAD ; // Try the lock - TATAS if (TryLock (Self) > 0) { assert (_succ != Self , "invariant") ; assert (_owner == Self , "invariant") ; assert (_Responsible != Self , "invariant") ; return ; } if (TrySpin (Self) > 0) { assert (_owner == Self , "invariant") ; assert (_succ != Self , "invariant") ; assert (_Responsible != Self , "invariant") ; return ; }//以上代码是在没有获得锁的情况下再次尝试获取锁 // 省略部分代码 // 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ; ObjectWaiter node(Self) ; Self->_ParkEvent->reset() ; node._prev = (ObjectWaiter *) 0xBAD ; node.TState = ObjectWaiter::TS_CXQ ; // 通过CAS把node节点push到_cxq列表中 ObjectWaiter * nxt ; for (;;) { node._next = nxt = _cxq ; if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ; // Interference - the CAS failed because _cxq changed. Just retry. // As an optional optimization we retry the lock. if (TryLock (Self) > 0) { assert (_succ != Self , "invariant") ; assert (_owner == Self , "invariant") ; assert (_Responsible != Self , "invariant") ; return ; } } // 省略部分代码 for (;;) { // 线程在被挂起前做一下挣扎,看能不能获取到锁 if (TryLock (Self) > 0) break ; assert (_owner != Self, "invariant") ; if ((SyncFlags & 2) && _Responsible == NULL) { Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ; } // park self if (_Responsible == Self || (SyncFlags & 1)) { TEVENT (Inflated enter - park TIMED) ; Self->_ParkEvent->park ((jlong) RecheckInterval) ; // Increase the RecheckInterval, but clamp the value. RecheckInterval *= 8 ; if (RecheckInterval > 1000) RecheckInterval = 1000 ; } else { TEVENT (Inflated enter - park UNTIMED) ; // 通过park将当前线程挂起(由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提
高内存利用率需要将暂时不能运行的进程(处于就绪或阻塞队列的进程)调出到磁盘),等待被唤醒 Self->_ParkEvent->park() ; } if (TryLock(Self) > 0) break ; // 省略部分代码 } // 省略部分代码 }
当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁,TryLock方法实现如下:
int ObjectMonitor::TryLock (Thread * Self) { for (;;) { void * own = _owner ; if (own != NULL) return 0 ; if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) { // Either guarantee _recursions == 0 or set _recursions = 0. assert (_recursions == 0, "invariant") ; assert (_owner == Self, "invariant") ; // CONSIDER: set or assert that OwnerIsThread == 1 return 1 ; } // The lock had been free momentarily, but we lost the race to the lock. // Interference -- the CAS failed. // We can either return -1 or retry. // Retry doesn‘t make as much sense because the lock was just acquired. if (true) return -1 ; } }
5、monitor释放
6、monitor是重量级锁
可以看到ObjectMonitor的函数调用中会涉及到Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数,执行同步代码块,没有竞争到锁的对象会park()被挂起,竞争到锁的线程会unpark()唤醒。这个时候就会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以synchronized是Java语言中是一个重量级(Heavyweight)的操作。
内核:可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。
用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。
系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。
所有进程初始都运行于用户空间,此时即为用户运行状态(简称:用户态);但是当它调用系统调用执行某些操作时,例如 I/O调用,此时需要陷入内核中运行,我们就称进程处于内核运行态(或简称为内核态)。 系统调用的过程可以简单理解为:
由此可见用户态切换至内核态需要传递许多变量,同时内核还需要保护好用户态在切换时的一些寄存器值、变量等,以备内核态切换回用户态。这种切换就带来了大量的系统资源消耗,这就是在synchronized未优化之前,效率低的原因。
原文:https://www.cnblogs.com/zhai1997/p/13544523.html