关键字 synchronized
synchronized,每个java程序员都用过的关键字。它可以拿来修饰方法、属性、代码块,它是一把锁,用来线程同步 balala 。。。我们对于synchronized的基础知识大概就这么多。然而作为java并发编程的基础,java面试重灾区,我们需要更深入的来了解它。它的底层实现原理,特点等等。
提醒:本文有大量的C++代码,读起来会很痛苦,不过没关系,尝试坚持一下,多少会有些收获。
monitorenter指令
public class SychronizeTest {
private static final Object lock = new Object();
public static void main(String[] args) {
synchronized (lock){
int i=1;
}
}
}
我们使用javap -c SychornizeTest.class拿到上面的类的字节码如下,可以看到第5行第9行有monitorenter 和monitorexit两行指令。我们知道这两个指令便是synchronized实现同步的关键。接下来我们一层层去openJdk源码中去找加锁过程的具体实现。
public class com.fc.study.SychoronizeTest {
public com.fc.study.SychoronizeTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field lock:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: iconst_1
7: istore_2
8: aload_1
9: monitorexit
10: goto 18
13: astore_3
14: aload_1
15: monitorexit
16: aload_3
17: athrow
18: return
Exception table:
from to target type
6 10 13 any
13 16 13 any
static {};
Code:
0: new #3 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #2 // Field lock:Ljava/lang/Object;
10: return
}
ObjectSynchronizer::enter
通过查找,我们发现在hotspot实现中有这么一个类,ObjectSynchronizer。在这个类中获取到了对象头markword,并且尝试获取锁。如果不成功则会生成一个monitor调用它的enter方法。
void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, TRAPS) {
if (obj->klass()->is_value_based()) {
handle_sync_on_value_based_class(obj, THREAD);
}
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::revoke(obj, THREAD);
} else {
BiasedLocking::revoke_at_safepoint(obj);
}
}
// 获取对象头的markword
markWord mark = obj->mark();
assert(!mark.has_bias_pattern(), "should not see bias pattern here");
// 如果 mark word 是无锁态的,直接尝试获取锁
if (mark.is_neutral()) {
lock->set_displaced_header(mark);
if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) {
return;
}
} else if (mark.has_locker() &&
THREAD->is_lock_owned((address)mark.locker())) {
assert(lock != mark.locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark().value(), "don't relock with same BasicLock");
lock->set_displaced_header(markWord::from_pointer(NULL));
return;
}
lock->set_displaced_header(markWord::unused_mark());
while (true) {
ObjectMonitor* monitor = inflate(THREAD, obj(), inflate_cause_monitor_enter);
if (monitor->enter(THREAD)) {
return;
}
}
}
ObjcetMonitor::enter
我们继续往下跟.
下面是ObjectMonitor主要的成员变量,我们可以看到它有markword,当前占有monitor的线程id,重入计数等很多变量,这些变量在后面的加锁过程都会用到。除此之外,我们可以看到openJdk做了一些很精巧的设计,它在_header变量和_owner变量中间加了一些东西将两个变量分割在不同的缓存行里,这样可以提高两个变量并发访问的效率(关于缓存行有兴趣的读者可以去查一下资料)。我们继续。
JVMCI_ONLY(friend class JVMCIVMStructs;)
static OopStorage* _oop_storage;
// The sync code expects the header field to be at offset zero (0).
// Enforced by the assert() in header_addr().
volatile markWord _header; // 对象头的markword displaced object header word - mark
WeakHandle _object; // backward object pointer
// 2.将header和owner分割在不同的缓存行里--为了提升读写效率
// Separate _header and _owner on different cache lines since both can
// have busy multi-threaded access. _header and _object are set at initial
// inflation. The _object does not change, so it is a good choice to share
// its cache line with _header.
DEFINE_PAD_MINUS_SIZE(0, OM_CACHE_LINE_SIZE, sizeof(volatile markWord) +
sizeof(WeakHandle));
// Used by async deflation as a marker in the _owner field:
#define DEFLATER_MARKER reinterpret_cast<void*>(-1)
void* _owner; // 3.拥有对象monitor的线程 pointer to owning thread OR BasicLock
volatile jlong _previous_owner_tid; // 上一个拥有monitor的线程的id thread id of the previous owner of
DEFINE_PAD_MINUS_SIZE(1, OM_CACHE_LINE_SIZE, sizeof(void*) +
sizeof(volatile jlong));
ObjectMonitor* _next_om; // Next ObjectMonitor* linkage
volatile intx _recursions; // 一个计数,标记monitor 进入的次数,初始为0recursion count, 0 for first entry
ObjectWaiter* volatile _EntryList; // Threads blocked on entry or reentry.
// The list is actually composed of WaitNodes,
// acting as proxies for Threads.
ObjectWaiter* volatile _cxq; // 阻塞线程列表 LL of recently-arrived threads blocked on entry.
Thread* volatile _succ; // Heir presumptive thread - used for futile wakeup throttling
Thread* volatile _Responsible;
volatile int _Spinner; // for exit->spinner handoff optimization
volatile int _SpinDuration;
jint _contentions; // Number of active contentions in enter(). It is used by is_busy()
// along with other fields to determine if an ObjectMonitor can be
// deflated. It is also used by the async deflation protocol. See
// ObjectMonitor::deflate_monitor().
protected:
ObjectWaiter* volatile _WaitSet; // LL of threads wait()ing on the monitor
volatile jint _waiters; // number of waiting threads
private:
volatile int _WaitSetLock; // protects Wait Queue - simple spinlock
加锁源码
加锁的主方法是下面这个enter方法,我删掉了多余的英文注释,并用中文做了简单注释。我们看到monitor会首先取到自己的线程,然后去尝试将_owner设置成当前线程,用CAS的方式。如果当前_owner是NULL,表示此时没有线程拥有monitor,则直接返回成功。如果_owner是当前线程,则重入计数加一,返回成功。否则往下执行EnterI方法,进行入队阻塞。
bool ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD;
// 步骤1
// try_set_owner_from
// 就是用cas的方法去设置 _owner 的值,它的返回值是 _owner的prev值
// 如果前值是NULL 则表示第一次加锁成功
void* cur = try_set_owner_from(NULL, Self);
if (cur == NULL) {
assert(_recursions == 0, "invariant");
return true;
}
// 如果_owner前值是当前线程,则表示是当前线程重入这个monitor,重入计数+1
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions++;
return true;
}
if (Self->is_lock_owned((address)cur)) {
assert(_recursions == 0, "internal state error");
_recursions = 1;
set_owner_from_BasicLock(cur, Self); // Convert from BasicLock* to Thread*.
return true;
}
// We've encountered genuine contention.
assert(Self->_Stalled == 0, "invariant");
Self->_Stalled = intptr_t(this);
// 步骤二
// 在将自己入队之前尝试做一次自旋获取锁,如果自选成功,则当前线程占有monitor
if (TrySpin(Self) > 0) {
assert(owner_raw() == Self, "must be Self: owner=" INTPTR_FORMAT, p2i(owner_raw()));
assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
assert(object()->mark() == markWord::encode(this),
"object mark must match encoded this: mark=" INTPTR_FORMAT
", encoded this=" INTPTR_FORMAT, object()->mark().value(),
markWord::encode(this).value());
Self->_Stalled = 0;
return true;
}
assert(owner_raw() != Self, "invariant");
assert(_succ != Self, "invariant");
JavaThread * jt = Self->as_Java_thread();
assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
assert(jt->thread_state() != _thread_blocked, "invariant");
// Keep track of contention for JVM/TI and M&M queries.
add_to_contentions(1);
if (is_being_async_deflated()) {
// Async deflation is in progress and our contentions increment
// above lost the race to async deflation. Undo the work and
// force the caller to retry.
const oop l_object = object();
if (l_object != NULL) {
// Attempt to restore the header/dmw to the object's header so that
// we only retry once if the deflater thread happens to be slow.
install_displaced_markword_in_object(l_object);
}
Self->_Stalled = 0;
add_to_contentions(-1);
return false;
}
JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)
EventJavaMonitorEnter event;
if (event.is_started()) {
event.set_monitorClass(object()->klass());
// Set an address that is 'unique enough', such that events close in
// time and with the same address are likely (but not guaranteed) to
// belong to the same object.
event.set_address((uintptr_t)this);
}
{ // Change java thread status to indicate blocked on monitor enter.
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
Self->set_current_pending_monitor(this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for (;;) {
jt->set_suspend_equivalent();
// 进入EnterI方法,这个方法里包含了入队阻塞的操作
EnterI(THREAD);
if (!ExitSuspendEquivalent(jt)) break;
_recursions = 0;
_succ = NULL;
exit(false, Self);
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
add_to_contentions(-1);
assert(contentions() >= 0, "must not be negative: contentions=%d", contentions());
Self->_Stalled = 0;
// Must either set _recursions = 0 or ASSERT _recursions == 0.
assert(_recursions == 0, "invariant");
assert(owner_raw() == Self, "invariant");
assert(_succ != Self, "invariant");
assert(object()->mark() == markWord::encode(this), "invariant");
}
OM_PERFDATA_OP(ContendedLockAttempts, inc());
DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_entered()) {
JvmtiExport::post_monitor_contended_entered(jt, this);
}
if (event.should_commit()) {
event.set_previousOwner((uintptr_t)_previous_owner_tid);
event.commit();
return true;
}
下面是 EnterI 方法,这个方法包含了入队阻塞的逻辑。可以看到在入队的循环中,monitor主要执行两个逻辑判断,TryLock失败则入队,入队后阻塞。被唤醒之后不断重试。
void ObjectMonitor::EnterI(TRAPS) {
Thread * const Self = THREAD;
assert(Self->as_Java_thread()->thread_state() == _thread_blocked, "invariant");
// Try the lock - TATAS
if (TryLock (Self) > 0) {
assert(_succ != Self, "invariant");
assert(owner_raw() == Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
if (try_set_owner_from(DEFLATER_MARKER, Self) == DEFLATER_MARKER) {
add_to_contentions(1);
assert(_succ != Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
assert(InitDone, "Unexpectedly not initialized");
// We try one round of spinning *before* enqueueing Self.
//
// If the _owner is ready but OFFPROC we could use a YieldTo()
// operation to donate the remainder of this thread's quantum
// to the owner. This has subtle but beneficial affinity
// effects.
if (TrySpin(Self) > 0) {
assert(owner_raw() == Self, "invariant");
assert(_succ != Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
// The Spin failed -- Enqueue and park the thread ...
assert(_succ != Self, "invariant");
assert(owner_raw() != Self, "invariant");
assert(_Responsible != Self, "invariant");
ObjectWaiter node(Self);
Self->_ParkEvent->reset();
node._prev = (ObjectWaiter *) 0xBAD;
node.TState = ObjectWaiter::TS_CXQ;
// 步骤三
// 将当前的monitor 入队到_cxq,如果入队成功则跳出循环进行下一步,否则尝试获取锁(失败则继续循环,成功则返回)
ObjectWaiter * nxt;
for (;;) {
node._next = nxt = _cxq;
if (Atomic::cmpxchg(&_cxq, nxt, &node) == 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_raw() == Self, "invariant");
assert(_Responsible != Self, "invariant");
return;
}
}
if (nxt == NULL && _EntryList == NULL) {
// Try to assume the role of responsible thread for the monitor.
// CONSIDER: ST vs CAS vs { if (Responsible==null) Responsible=Self }
Atomic::replace_if_null(&_Responsible, Self);
}
int nWakeups = 0;
int recheckInterval = 1;
// 步骤四:
// 当前已经入队成功
// 尝试获取锁,成功则跳出循环往下执行,表示获取到锁
// 不成功 则parkself 阻塞
for (;;) {
if (TryLock(Self) > 0) break;
assert(owner_raw() != Self, "invariant");
// park self
if (_Responsible == Self) {
Self->_ParkEvent->park((jlong) recheckInterval);
// Increase the recheckInterval, but clamp the value.
recheckInterval *= 8;
if (recheckInterval > MAX_RECHECK_INTERVAL) {
recheckInterval = MAX_RECHECK_INTERVAL;
}
} else {
Self->_ParkEvent->park();
}
if (TryLock(Self) > 0) break;
if (try_set_owner_from(DEFLATER_MARKER, Self) == DEFLATER_MARKER) {
add_to_contentions(1);
break;
}
OM_PERFDATA_OP(FutileWakeups, inc());
++nWakeups;
if (TrySpin(Self) > 0) break;
if (_succ == Self) _succ = NULL;
OrderAccess::fence();
}
assert(owner_raw() == Self, "invariant");
UnlinkAfterAcquire(Self, &node);
if (_succ == Self) _succ = NULL;
assert(_succ != Self, "invariant");
if (_Responsible == Self) {
_Responsible = NULL;
OrderAccess::fence(); // Dekker pivot-point
}
return;
}
下面是 TryLock 实现。逻辑很简单,就是尝试去设置_owner为当前线程,设置成功则返回1。
int ObjectMonitor::TryLock(Thread * Self) {
void* own = owner_raw();
if (own != NULL) return 0;
if (try_set_owner_from(NULL, Self) == NULL) {
assert(_recursions == 0, "invariant");
return 1;
}
return -1;
}
总结
以上,我们可以得出如下结论。
synchronized 关键字由 monitorenter|exit两个指令支持实现的
在jdk层面,enter方法主要由 ObjectSychronizer::enter 和 ObjectMonitor::enter 实现。
ObjectSychronizer 中,jvm获取对象的markword判断,如果无锁态的话则直接尝试获取锁,不会进入monitor的状态。
ObjectMonitor 对象中,标记了当前持有线程_owner,阻塞队列_cxq,重入次数_recursions等。在加锁过程中多次进行TryLock(直接设置当前线程为_owner),多次失败之后最终会将当前线程入队,并阻塞。被唤醒后在此尝试获取锁,成功则退出,失败则继续尝试。
以上便是 jdk 源码层面的 synchoronized 解读。