引言
上篇文章,我们介绍了锁的入口、偏向锁的偏向与撤销。本文继续讲解锁的膨胀。
轻量级锁
系统未启用、偏向失败,synchronized 并不会立即升级成重量级锁,而是尝试使用轻量级锁的一种优化手段,此时 markWord 会变成轻量级锁结构。轻量级锁之所以能够提升性能是依据“对于绝大部分的锁,在整个同步周期内都不存在竞争”。
轻量级锁 markWord 结构
LockRecord
如果升级成轻量级锁时,当前的 markWord 结构就会变为指向 Lock Record 的指针。其实 Lock Record 并不是在锁升级为轻量级锁时产生的,它在偏向锁阶段就已经产生了。
slow_enter
1 | /** |
膨胀到轻量级锁时,首先通过对象的 markWord 判断锁的状态。如果是无锁态(锁标记为 01),那么将当前对象的 markWord 复制一份到 LockRecord,通过 CAS 将 markWord 指向 LockRecord。如果成功,说明没有发生竞争,该线程获取到了锁。
如果当前对象有锁(标记为 00)并且当前线程就是锁的持有者,那么说明是锁的重入,此时将 LockRecord 的 markWord 置空。
除此,说明多个线程在竞争锁,那么需要将 RecordLock 中的 markWord 锁标记改为 11,然后进行膨胀。
重量级锁
此时就属于传统的锁了,markWord 变为重量锁结构,也就是说指向 monitor 的指针:
monitor 对象
当 markWord 指向 monitor 时,说明此时已经处于重量级锁状态了。我们看下它的结构:
1 | /** |
了解了 monitor 对象结构之后,我们看下锁的膨胀过程:
inflate
1 | /** |
锁的膨胀其实就是获取 ObjectMonitor 对象的过程,那么锁的竞争发生在什么地方呢?不知道大家是否还记得 inflate 方法调用的地方:
1 | // 开始膨胀 |
当 inflate 方法拿到 ObjectMonitor 对象之后,就会调用它的 enter 方法,ObjectMonitor::enter 方法就是锁竞争的核心方法了!
enter
1 | /** |
enterI
获取锁失败时,是通过 enterI 方法进行自旋等待锁释放。所以我们深入 enterI 看看具体的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
assert (Self->is_Java_thread(), "invariant") ;
assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;
// 尝试获取一次锁,万一成了呢
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
DeferredInitialize () ;
// 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 == Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
// 如果还是没有获取到锁,那么就需要入队挂起了
assert (_succ != Self , "invariant") ;
assert (_owner != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
// Enqueue "Self" on ObjectMonitor's _cxq.
//
// Node acts as a proxy for Self.
// As an aside, if were to ever rewrite the synchronization code mostly
// in Java, WaitNodes, ObjectMonitors, and Events would become 1st-class
// Java objects. This would avoid awkward lifecycle and liveness issues,
// as well as eliminate a subset of ABA issues.
// TODO: eliminate ObjectWaiter and enqueue either Threads or Events.
//
// 将当前线程封装成 ObjectWaiter 对象
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
ObjectWaiter * nxt ;
// 通过自旋的方式将线程存入 _cxq
for (;;) {
// 采用头插法入队 _cxq:thread-2 -> thread-1 -> thread-0
node._next = nxt = _cxq ;
// 存入成功则跳出循环,每次修改 _cxq 头结点为新入队节点
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// 如果 _cxq 变更导致 CAS 失败,只需要重试就行了。当然,这可以再尝试获取锁一次
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
/*
* 尝试为 monitor 对象分配一个 “责任” 线程,如果竞争的线程都挂起了,那么即使 monitor 解锁了,也没有线程能获取到。
* 所以需要有一个定时挂起的线程来负责检查并唤醒相应线程,该线程就是 “责任” 线程。
*/
if ((SyncFlags & 16) == 0 && 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::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// The lock have been released while this thread was occupied queueing
// itself onto _cxq. To close the race and avoid "stranding" and
// progress-liveness failure we must resample-retry _owner before parking.
// Note the Dekker/Lamport duality: ST cxq; MEMBAR; LD Owner.
// In this case the ST-MEMBAR is accomplished with CAS().
//
// TODO: Defer all thread state transitions until park-time.
// Since state transitions are heavy and inefficient we'd like
// to defer the state transitions until absolutely necessary,
// and in doing so avoid some transitions ...
TEVENT (Inflated enter - Contention) ;
int nWakeups = 0 ;
int RecheckInterval = 1 ;
for (;;) {
// 拿到锁退出
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
// 不存在 “责任” 线程时,设置 “责任” 线程
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// 如果当前线程是 “责任” 线程,那么定时挂起当前线程,否则直接挂起
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) ;
Self->_ParkEvent->park() ;
}
// 尝试获取锁
if (TryLock(Self) > 0) break ;
// 记录无效的唤醒次数
TEVENT (Inflated enter - Futile wakeup) ;
if (ObjectMonitor::_sync_FutileWakeups != NULL) {
ObjectMonitor::_sync_FutileWakeups->inc() ;
}
++ nWakeups ;
// Assuming this is not a spurious wakeup we'll normally find _succ == Self.
// We can defer clearing _succ until after the spin completes
// TrySpin() must tolerate being called with _succ == Self.
// Try yet another round of adaptive spinning.
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
// We can find that we were unpark()ed and redesignated _succ while
// we were spinning. That's harmless. If we iterate and call park(),
// park() will consume the event and return immediately and we'll
// just spin again. This pattern can repeat, leaving _succ to simply
// spin on a CPU. Enable Knob_ResetEvent to clear pending unparks().
// Alternately, we can sample fired() here, and if set, forgo spinning
// in the next iteration.
if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
Self->_ParkEvent->reset() ;
OrderAccess::fence() ;
}
if (_succ == Self) _succ = NULL ;
// Invariant: after clearing _succ a thread *must* retry _owner before parking.
OrderAccess::fence() ;
}
// 走到这里说明已经获取了锁
assert (_owner == Self , "invariant") ;
assert (object() != NULL , "invariant") ;
// I'd like to write:
// guarantee (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
// but as we're at a safepoint that's not safe.
// 获取到锁之后的取消链接
UnlinkAfterAcquire (Self, &node) ;
// 如果后继线程是当前线程,将后继线程置 null
if (_succ == Self) _succ = NULL ;
assert (_succ != Self, "invariant") ;
// 如果当前线程是 “责任” 线程
if (_Responsible == Self) {
// “责任” 线程置 null
_Responsible = NULL ;
OrderAccess::fence(); // Dekker pivot-point
// We may leave threads on cxq|EntryList without a designated
// "Responsible" thread. This is benign. When this thread subsequently
// exits the monitor it can "see" such preexisting "old" threads --
// threads that arrived on the cxq|EntryList before the fence, above --
// by LDing cxq|EntryList. Newly arrived threads -- that is, threads
// that arrive on cxq after the ST:MEMBAR, above -- will set Responsible
// non-null and elect a new "Responsible" timer thread.
//
// This thread executes:
// ST Responsible=null; MEMBAR (in enter epilog - here)
// LD cxq|EntryList (in subsequent exit)
//
// Entering threads in the slow/contended path execute:
// ST cxq=nonnull; MEMBAR; LD Responsible (in enter prolog)
// The (ST cxq; MEMBAR) is accomplished with CAS().
//
// The MEMBAR, above, prevents the LD of cxq|EntryList in the subsequent
// exit operation from floating above the ST Responsible=null.
}
// We've acquired ownership with CAS().
// CAS is serializing -- it has MEMBAR/FENCE-equivalent semantics.
// But since the CAS() this thread may have also stored into _succ,
// EntryList, cxq or Responsible. These meta-data updates must be
// visible __before this thread subsequently drops the lock.
// Consider what could occur if we didn't enforce this constraint --
// STs to monitor meta-data and user-data could reorder with (become
// visible after) the ST in exit that drops ownership of the lock.
// Some other thread could then acquire the lock, but observe inconsistent
// or old monitor meta-data and heap data. That violates the JMM.
// To that end, the 1-0 exit() operation must have at least STST|LDST
// "release" barrier semantics. Specifically, there must be at least a
// STST|LDST barrier in exit() before the ST of null into _owner that drops
// the lock. The barrier ensures that changes to monitor meta-data and data
// protected by the lock will be visible before we release the lock, and
// therefore before some other thread (CPU) has a chance to acquire the lock.
// See also: http://gee.cs.oswego.edu/dl/jmm/cookbook.html.
//
// Critically, any prior STs to _succ or EntryList must be visible before
// the ST of null into _owner in the *subsequent* (following) corresponding
// monitorexit. Recall too, that in 1-0 mode monitorexit does not necessarily
// execute a serializing instruction.
if (SyncFlags & 8) {
OrderAccess::fence() ;
}
return ;
}tryLock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// Caveat: TryLock() is not necessarily serializing if it returns failure.
// Callers must compensate as needed.
int ObjectMonitor::TryLock (Thread * Self) {
for (;;) {
// monitor 当前的持有者
void * own = _owner ;
// 如果不为 null,说明已经有归属,那么上锁肯定失败
if (own != NULL) return 0 ;
// 如果 CAS 成功,说明获取到了锁
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
// 重复次数肯定为 0,毕竟刚获得锁
assert (_recursions == 0, "invariant") ;
// 持有者是否为自己
assert (_owner == Self, "invariant") ;
// 说明上锁成功了
return 1 ;
}
// 上锁失败
if (true) return -1 ;
}
}总结
enter 方法主要做了以下几件事:- 通过 CAS 操作,将当前线程设置到 monitor 对象的 _owner 字段中以获取锁,成功则返回。
- 如果 _owner 指向的就是当前线程,说明发生了锁重入,递增 _recursions。
- 如果当前线程获取锁成功,那么就将 _recursions 设置为 1,同时 _owner 指向当前线程。
- 如果失败,需要等待锁的释放。
如果获取锁失败,那么就需要通过自旋的方式等待锁的释放,也就是 enterI 方法要做的事:
- 将当前线程封装成状态为 TS_CXQ 的 ObjectWaiter。
- 通过自旋操作将 ObjectWaiter 节点 push 到 _cxq 队列中。
- 进入到 _cxq 队列中的节点继续通过自旋的方式尝试获取锁,如果达到指定阈值还未成功,则通过 park 将自己挂起等待被唤醒。
锁的释放
轻量级锁的释放
轻量级锁的释放是通过 monitorexit 调用完成的。
monitorexit
1 | // monitorexit 指令定义 |
slow_exit
1 | // 由 fast_exit 完成锁的释放 |
fast_exit
1 | void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) { |
轻量级锁的释放很简单,只是将当前线程栈中锁记录的 displaced_header 头替换回对象 markWord 即可。如果替换失败,那么需要膨胀成重量级锁,实现重量级锁的释放逻辑。
重量级锁的释放
重量级锁的释放由 ObjectMonitor::exit 方法来实现。
1 | void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) { |
ExitEpilog
ExitEpilog 负责节点的唤醒。
1 | void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) { |