Synchronized的原理(C++层)
原理描述:
重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现。当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。这就是说为什么重量级线程开销成本很高。
无论是ACC_SYNCHRONIZED(方法锁)还是monitorenter、monitorexit都是基于Monitor实现的,在Java虚拟机(HotSpot)中,Monitor是基于C++实现的,由ObjectMonitor类实现。
ObjectMonitor类中提供了几个方法,如enter、exit、wait、notify、notifyAll等。sychronized加锁的时候,会调用objectMonitor的enter方法,解锁的时候会调用exit方法。
monitor(管程/监视器)
我们可以把Monitor理解为一个同步工具,也可以描述为一种同步机制。所有的Java对象是天生的Monitor,每个object的对象里markOop->monitor() 里可以保存ObjectMonitor的对象。
要了解monitor需要具备几个知识点
1.java对象头中的与锁有关的区域为Mark Word(详细上边看java对象模型章节)
1.Mark Word中lock标志位(2bit) 分别对应无锁(01)、偏向锁(01)、轻量级锁(00)、重量级锁(10)
2.无锁(01)、偏向锁(01)由1bit的位置标示是否为偏向锁, 无锁(0), 偏向锁(1)
3.偏向锁用不着monitor, Mark Word中存放的是线程ID, 用的线程ID判断.
4.轻量级锁用不着monitor,Mark Word中存放是指向当前线程的栈帧中创建锁记录空间的指针
5.重量级锁是基于monitor实现的, 这时Mark Word中存放的就是指向monitor对象的指针
6.synchronized在1.5之前只有重量级锁, 也称为传统锁. 1.6才引入偏向锁和轻量级锁
C++源码:
oop.hpp下的oopDesc类是JVM对象的顶级基类,所以每个object对象都包含markOop
oop.hpp,每个 Java Object 在 JVM 内部都有一个 native 的 C++ 对象 oop/oopDesc 与之对应。先在oop.hpp中看oopDesc的定义:
class oopDesc {//顶层基类
friend class VMStructs;
private:
volatile markOop _mark;//理解为对象头也就是每个对象的mark work头
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
......
_mark 被声明在 oopDesc 类的顶部,所以这个 _mark 可以认为是一个 头部, 也就是上面那个图种提到的头部保存了一些重要的状态和标识信息,在markOop.hpp文件中有一些注释说明markOop的内存布局:
markOop.hpp 中 markOopDesc继承自oopDesc,
class BasicLock;
class objectMonitor;
class JavaThread;
class markOopDesc: public oopDesc {
private:
// Conversion
uintptr_ t value() const { return (uintptr_ _t) this; }
并扩展了自己的monitor方法,这个方法返回一个ObjectMonitor指针对象:这个ObjectMonitor 其实就是对象监视器
objectMonitor* monitor() const {
assert(has_monitor(), "check");
// Use xor instead of &~ to provide one extra tag-bit check.
return (ObjectMonitor*) (value() ^ monitor_value );
}
objectMonitor.hpp, 在hotspot虚拟机中,采用ObjectMonitor类来实现monitor:
ObjectMonitor() {
_header = NULL; //markOop对象头
_count = 0; //获取锁的次数(释放会-1)(**重点**)
_waiters = 0, //等待线程数
_recursions = 0; //锁的重入次数(释放不会-1)
_object = NULL;
_owner = NULL; //指向获得ObjectMonitor对象的线程(**重点**)
_WaitSet = NULL; //处于wait状态的线程, 会被加入waitSet(**重点**)
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //JVM 为每个尝试进入synchronized代码段的Java线程创建一个ObjectWaiter并添加到_cxq队列中
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程, 由ObjectWaiter组成双向链表, JVM会从该链表中取出一个ObjectWaiter并唤醒对应的java线程(**重点**)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0; //监视器钱一个拥有者的线程id
}
ObjectMonitor类重要方法
获得锁enter
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
//通过CAS尝试把monitor的`_owner`字段设置为当前线程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
//获取锁失败
if (cur == NULL) { 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 ;
}
//省略部分代码。
//通过自旋执行ObjectMonitor::EnterI方法等待锁的释放
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 (Self) ;
jt->java_suspend_self();
}
}
释放锁exit
void ATTR ObjectMonitor::exit(TRAPS) {
Thread * Self = THREAD ;
//如果当前线程不是Monitor的所有者
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) { //
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
//如果_recursions次数不为0.自减
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
省略部分代码,根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。
等待唤醒wait
封装 ObjectWaiter 对象并将其放入 _WaitSet 队列,并调用 park()将线程挂起。
// Wait/Notify/NotifyAll
// Note: a subset of changes to ObjectMonitor::wait()
// will need to be replicated in complete_exit above
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
Thread * const Self = THREAD ;
assert(Self->is_Java_thread(), "Must be Java thread!");
JavaThread *jt = (JavaThread *)THREAD;
DeferredInitialize () ;
// Throw IMSX or IEX.
CHECK_OWNER();//检查objectMonitor对象是否指向本线程(即是否获得锁)
// ... 省略中间的代码
// create a node to be put into the queue
// Critically, after we reset() the event but prior to park(), we must check
// for a pending interrupt.
// 封装了一个ObjectWaiter对象
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
Self->_ParkEvent->reset() ;
OrderAccess::fence();//内存屏障 // ST into Event; membar ; LD interrupted-flag
// Enter the waiting queue, which is a circular doubly linked list in this case
// but it could be a priority queue or any data structure.
// _WaitSetLock protects the wait queue. Normally the wait queue is accessed only
// by the the owner of the monitor *except* in the case where park()
// returns because of a timeout of interrupt. Contention is exceptionally rare
// so we use a simple spin-lock instead of a heavier-weight blocking lock.
//将ObjectWaiter放入 _WaitSet中
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
AddWaiter (&node) ;
Thread::SpinRelease (&_WaitSetLock) ;
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
intptr_t save = _recursions; // record the old recursion count
_waiters++; // increment the number of waiters
_recursions = 0; // set the recursion level to be 1
exit (true, Self) ; // exit the monitor
guarantee (_owner != Self, "invariant") ;
//.....省略中间代码
// The thread is on the WaitSet list - now park() it.
// On MP systems it's conceivable that a brief spin before we park
// could be profitable.
// TODO-FIXME: change the following logic to a loop of the form
// while (!timeout && !interrupted && _notified == 0) park()
int ret = OS_OK ;
int WasNotified = 0 ;
{ // State transition wrappers
OSThread* osthread = Self->osthread();
OSThreadWaitState osts(osthread, true);
{
ThreadBlockInVM tbivm(jt);
// Thread is in thread_blocked state and oop access is unsafe.
jt->set_suspend_equivalent();
if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
// Intentionally empty
} else
if (node._notified == 0) {
if (millis <= 0) { // 调用park()将线程挂起
Self->_ParkEvent->park () ;
} else {
ret = Self->_ParkEvent->park (millis) ;
}
}
.......
}
通知唤醒notify
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();//同样先检查objectMonitor对象是否指向本线程
if (_WaitSet == NULL) {//判断wait队列是否为空
TEVENT (Empty-Notify) ;
return ;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
int Policy = Knob_MoveNotifyee ;
// 这个 WaitSet - notify 很了然
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
ObjectWaiter * iterator = DequeueWaiter() ;//dequeue _WaitSet 队列
if (iterator != NULL) {//不为空,然后接下去就是一系列的判断,最后去唤醒
//.......
}
}
[synchronized原理及优化] https://www.jianshu.com/p/435c20a64da1
[synchronized原理] https://www.cnblogs.com/wuzhenzhao/p/10250801.html