Skip to content

Commit fb291f7

Browse files
committed
[GR-34100] Use non-object owner thread identifiers in JavaMonitor.
PullRequest: graal/13245
2 parents d7ff091 + 1d13963 commit fb291f7

File tree

5 files changed

+188
-128
lines changed

5 files changed

+188
-128
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
package com.oracle.svm.core.monitor;
2828

2929
import org.graalvm.nativeimage.CurrentIsolate;
30+
import org.graalvm.nativeimage.IsolateThread;
3031

3132
import com.oracle.svm.core.Uninterruptible;
3233
import com.oracle.svm.core.jfr.JfrTicks;
3334
import com.oracle.svm.core.jfr.SubstrateJVM;
3435
import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent;
36+
import com.oracle.svm.core.thread.JavaThreads;
3537
import com.oracle.svm.core.util.VMError;
3638

3739
import jdk.internal.misc.Unsafe;
@@ -42,6 +44,13 @@
4244
*
4345
* Only the relevant methods from the JDK sources have been kept. Some additional Native
4446
* Image-specific functionality has been added.
47+
*
48+
* Main differences to the JDK implementation:
49+
* <ul>
50+
* <li>Uses the {@linkplain #setState synchronization state} to store a
51+
* {@linkplain #getThreadIdentity numeric identifier of the owner thread} and {@link #acquisitions}
52+
* to store the number of lock acquisitions, enabling various optimizations.</li>
53+
* </ul>
4554
*/
4655
public class JavaMonitor extends JavaMonitorQueuedSynchronizer {
4756
protected long latestJfrTid;
@@ -88,22 +97,21 @@ public void relockObject() {
8897
* or the object must be unlocked (if the object was rematerialized during deoptimization).
8998
* If the object is locked by another thread, lock elimination in the compiler has a serious
9099
* bug.
91-
*/
92-
Thread currentThread = Thread.currentThread();
93-
Thread ownerThread = getExclusiveOwnerThread();
94-
VMError.guarantee(ownerThread == null || ownerThread == currentThread, "Object that needs re-locking during deoptimization is already locked by another thread");
95-
96-
/*
100+
*
97101
* Since this code must be uninterruptible, we cannot just call lock.tryLock() but instead
98102
* replicate that logic here by using only direct field accesses.
99103
*/
100-
int oldState = getState();
101-
int newState = oldState + 1;
102-
VMError.guarantee(newState > 0, "Maximum lock count exceeded");
103-
104-
boolean success = U.compareAndSetInt(this, JavaMonitorQueuedSynchronizer.STATE, oldState, newState);
105-
VMError.guarantee(success, "Could not re-lock object during deoptimization");
106-
setExclusiveOwnerThread(currentThread);
104+
long currentThread = getCurrentThreadIdentity();
105+
long ownerThread = getState();
106+
if (ownerThread == 0) {
107+
boolean success = compareAndSetState(0, currentThread);
108+
VMError.guarantee(success && acquisitions == 1, "Could not re-lock object during deoptimization");
109+
} else {
110+
VMError.guarantee(ownerThread == currentThread, "Object that needs re-locking during deoptimization is already locked by another thread");
111+
112+
acquisitions++;
113+
VMError.guarantee(acquisitions > 0, "Maximum lock count exceeded");
114+
}
107115
}
108116

109117
/*
@@ -117,60 +125,100 @@ public void relockObject() {
117125

118126
private JavaMonitorConditionObject condition;
119127

128+
/**
129+
* We store the numeric identifier of the owner thread in the {@linkplain #setState
130+
* synchronization state} rather than using it for the number of acquisitions (recursive
131+
* entries) like {@code ReentrantLock} does. We use this field for the acquisition count
132+
* instead, which is accessed only by the thread holding the lock so that it does not need
133+
* {@code volatile} semantics. This also enables us to leave this field's value at 1 on release
134+
* so that the next thread acquiring the lock does not need to immediately write it.
135+
*/
136+
protected int acquisitions = 1;
137+
138+
/** {@inheritDoc} */
139+
@Override
140+
protected long getAcquisitions() {
141+
return acquisitions;
142+
}
143+
120144
// see ReentrantLock.NonFairSync.tryAcquire(int)
121145
@Override
122-
protected boolean tryAcquire(int acquires) {
123-
if (getState() == 0 && compareAndSetState(0, acquires)) {
124-
setExclusiveOwnerThread(Thread.currentThread());
146+
protected boolean tryAcquire(long acquires) {
147+
assert acquires > 0 && acquires == (int) acquires;
148+
if (getState() == 0 && compareAndSetState(0, getCurrentThreadIdentity())) {
149+
assert acquisitions == 1;
150+
acquisitions = (int) acquires;
125151
return true;
126152
}
127153
return false;
128154
}
129155

130156
// see ReentrantLock.Sync.tryLock()
131157
boolean tryLock() {
132-
Thread current = Thread.currentThread();
133-
int c = getState();
158+
long current = getCurrentThreadIdentity();
159+
long c = getState();
134160
if (c == 0) {
135-
if (compareAndSetState(0, 1)) {
136-
setExclusiveOwnerThread(current);
161+
if (compareAndSetState(0, current)) {
162+
assert acquisitions == 1;
137163
return true;
138164
}
139-
} else if (getExclusiveOwnerThread() == current) {
140-
if (++c < 0) { // overflow
165+
} else if (c == current) {
166+
int r = acquisitions + 1;
167+
if (r < 0) { // overflow
141168
throw new Error("Maximum lock count exceeded");
142169
}
143-
setState(c);
170+
// Note: protected by monitor and not required to be observable, no ordering needed
171+
acquisitions = r;
144172
return true;
145173
}
146174
return false;
147175
}
148176

149177
// see ReentrantLock.Sync.tryRelease()
150178
@Override
151-
protected boolean tryRelease(int releases) {
152-
int c = getState() - releases; // state must be 0 here
153-
if (getExclusiveOwnerThread() != Thread.currentThread()) {
154-
throw new IllegalMonitorStateException(); // owner is null and c =-1
179+
protected boolean tryRelease(long releases) {
180+
assert releases > 0;
181+
long current = getCurrentThreadIdentity();
182+
long c = getState();
183+
if (c != current) {
184+
throw new IllegalMonitorStateException();
155185
}
156-
boolean free = (c == 0);
186+
boolean free = (acquisitions == releases);
157187
if (free) {
158-
setExclusiveOwnerThread(null);
188+
acquisitions = 1;
189+
setState(0);
190+
} else {
191+
// Note: protected by monitor and not required to be observable, no ordering needed
192+
assert releases < acquisitions;
193+
acquisitions -= (int) releases;
159194
}
160-
setState(c);
161195
return free;
162196
}
163197

164198
// see ReentrantLock.Sync.isHeldExclusively()
165199
@Override
166200
protected boolean isHeldExclusively() {
167-
// While we must in general read state before owner,
168-
// we don't need to do so to check if current thread is owner
169-
return getExclusiveOwnerThread() == Thread.currentThread();
201+
return getState() == getCurrentThreadIdentity();
170202
}
171203

172-
// see ReentrantLock.Sync.lock()
204+
// see ReentrantLock.Sync.isLocked()
173205
boolean isLocked() {
174206
return getState() != 0;
175207
}
208+
209+
/**
210+
* Storing and comparing a {@link Thread} object to determine lock ownership needs heap address
211+
* computations and GC barriers, while we don't actually need to access the object, so we use a
212+
* unique numeric identifier instead. {@link IsolateThread} is readily available in a register,
213+
* which makes for compact fast path code, but if virtual threads are enabled, they migrate
214+
* between {@link IsolateThread}s, so we must use the unique ids assigned to {@link Thread}s.
215+
*/
216+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
217+
protected static long getCurrentThreadIdentity() {
218+
return JavaThreads.getCurrentThreadId();
219+
}
220+
221+
protected static long getThreadIdentity(Thread thread) {
222+
return JavaThreads.getThreadId(thread);
223+
}
176224
}

0 commit comments

Comments
 (0)