2727package com .oracle .svm .core .monitor ;
2828
2929import org .graalvm .nativeimage .CurrentIsolate ;
30+ import org .graalvm .nativeimage .IsolateThread ;
3031
3132import com .oracle .svm .core .Uninterruptible ;
3233import com .oracle .svm .core .jfr .JfrTicks ;
3334import com .oracle .svm .core .jfr .SubstrateJVM ;
3435import com .oracle .svm .core .jfr .events .JavaMonitorEnterEvent ;
36+ import com .oracle .svm .core .thread .JavaThreads ;
3537import com .oracle .svm .core .util .VMError ;
3638
3739import jdk .internal .misc .Unsafe ;
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 */
4655public 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