1 /* 2 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test id=default 26 * @summary Test virtual threads using synchronized 27 * @library /test/lib 28 * @modules java.base/java.lang:+open 29 * @run junit/othervm -Xint MonitorsTest 30 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=3 MonitorsTest 31 * @run junit/othervm -Xcomp -XX:-TieredCompilation MonitorsTest 32 * @run junit/othervm MonitorsTest 33 */ 34 35 /** 36 * @test id=gc 37 * @requires vm.debug 38 * @library /test/lib 39 * @modules java.base/java.lang:+open 40 * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MonitorsTest 41 */ 42 43 import java.util.concurrent.atomic.AtomicInteger; 44 import java.util.concurrent.*; 45 46 import org.junit.jupiter.api.Test; 47 import static org.junit.jupiter.api.Assertions.*; 48 49 class MonitorsTest { 50 final int CARRIER_COUNT = 8; 51 ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT); 52 53 static final Object globalLock = new Object(); 54 static volatile boolean finish = false; 55 static volatile int counter = 0; 56 57 ///////////////////////////////////////////////////////////////////// 58 //////////////////////////// BASIC TESTS //////////////////////////// 59 ///////////////////////////////////////////////////////////////////// 60 61 static final Runnable FOO = () -> { 62 Object lock = new Object(); 63 synchronized(lock) { 64 while(!finish) { 65 Thread.yield(); 66 } 67 } 68 System.out.println("Exiting FOO from thread " + Thread.currentThread().getName()); 69 }; 70 71 static final Runnable BAR = () -> { 72 synchronized(globalLock) { 73 counter++; 74 } 75 System.out.println("Exiting BAR from thread " + Thread.currentThread().getName()); 76 }; 77 78 /** 79 * Test yield while holding monitor. 80 */ 81 @Test 82 void testBasic() throws Exception { 83 final int VT_COUNT = CARRIER_COUNT; 84 85 // Create first batch of VT threads. 86 Thread firstBatch[] = new Thread[VT_COUNT]; 87 for (int i = 0; i < VT_COUNT; i++) { 88 firstBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO); 89 } 90 91 // Give time for all threads to reach Thread.yield 92 Thread.sleep(1000); 93 94 // Create second batch of VT threads. 95 Thread secondBatch[] = new Thread[VT_COUNT]; 96 for (int i = 0; i < VT_COUNT; i++) { 97 secondBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR); 98 } 99 100 while(counter != VT_COUNT) {} 101 102 finish = true; 103 104 for (int i = 0; i < VT_COUNT; i++) { 105 firstBatch[i].join(); 106 } 107 for (int i = 0; i < VT_COUNT; i++) { 108 secondBatch[i].join(); 109 } 110 } 111 112 static final Runnable BAR2 = () -> { 113 synchronized(globalLock) { 114 counter++; 115 } 116 recursive2(10); 117 System.out.println("Exiting BAR2 from thread " + Thread.currentThread().getName() + "with counter=" + counter); 118 }; 119 120 static void recursive2(int count) { 121 synchronized(Thread.currentThread()) { 122 if (count > 0) { 123 recursive2(count - 1); 124 } else { 125 synchronized(globalLock) { 126 counter++; 127 Thread.yield(); 128 } 129 } 130 } 131 } 132 133 /** 134 * Test yield while holding monitor with recursive locking. 135 */ 136 @Test 137 void testRecursive() throws Exception { 138 final int VT_COUNT = CARRIER_COUNT; 139 counter = 0; 140 finish = false; 141 142 // Create first batch of VT threads. 143 Thread firstBatch[] = new Thread[VT_COUNT]; 144 for (int i = 0; i < VT_COUNT; i++) { 145 firstBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO); 146 } 147 148 // Give time for all threads to reach Thread.yield 149 Thread.sleep(1000); 150 151 // Create second batch of VT threads. 152 Thread secondBatch[] = new Thread[VT_COUNT]; 153 for (int i = 0; i < VT_COUNT; i++) { 154 secondBatch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR2); 155 } 156 157 while(counter != 2*VT_COUNT) {} 158 159 finish = true; 160 161 for (int i = 0; i < VT_COUNT; i++) { 162 firstBatch[i].join(); 163 } 164 for (int i = 0; i < VT_COUNT; i++) { 165 secondBatch[i].join(); 166 } 167 } 168 169 static final Runnable FOO3 = () -> { 170 synchronized(globalLock) { 171 while(!finish) { 172 Thread.yield(); 173 } 174 } 175 System.out.println("Exiting FOO3 from thread " + Thread.currentThread().getName()); 176 }; 177 178 /** 179 * Test contention on monitorenter. 180 */ 181 @Test 182 void testContention() throws Exception { 183 final int VT_COUNT = CARRIER_COUNT * 8; 184 counter = 0; 185 finish = false; 186 187 // Create batch of VT threads. 188 Thread batch[] = new Thread[VT_COUNT]; 189 for (int i = 0; i < VT_COUNT; i++) { 190 batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO3); 191 } 192 193 // Give time for all threads to reach synchronized(globalLock) 194 Thread.sleep(2000); 195 196 finish = true; 197 198 for (int i = 0; i < VT_COUNT; i++) { 199 batch[i].join(); 200 } 201 } 202 203 ///////////////////////////////////////////////////////////////////// 204 //////////////////////////// MAIN TESTS ///////////////////////////// 205 ///////////////////////////////////////////////////////////////////// 206 207 static final int MONITORS_CNT = 12; 208 static Object[] globalLockArray; 209 static AtomicInteger workerCount = new AtomicInteger(0); 210 211 static void recursive4_1(int depth, int lockNumber) { 212 if (depth > 0) { 213 recursive4_1(depth - 1, lockNumber); 214 } else { 215 if (Math.random() < 0.5) { 216 Thread.yield(); 217 } 218 recursive4_2(lockNumber); 219 } 220 } 221 222 static void recursive4_2(int lockNumber) { 223 if (lockNumber + 2 <= MONITORS_CNT - 1) { 224 lockNumber += 2; 225 synchronized(globalLockArray[lockNumber]) { 226 Thread.yield(); 227 recursive4_2(lockNumber); 228 } 229 } 230 } 231 232 static final Runnable FOO4 = () -> { 233 while (!finish) { 234 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 235 synchronized(globalLockArray[lockNumber]) { 236 recursive4_1(lockNumber, lockNumber); 237 } 238 } 239 workerCount.getAndIncrement(); 240 System.out.println("Exiting FOO4 from thread " + Thread.currentThread().getName()); 241 }; 242 243 /** 244 * Test contention on monitorenter with extra monitors on stack shared by all threads. 245 */ 246 @Test 247 void testContentionMultipleMonitors() throws Exception { 248 final int VT_COUNT = CARRIER_COUNT * 8; 249 workerCount.getAndSet(0); 250 finish = false; 251 252 globalLockArray = new Object[MONITORS_CNT]; 253 for (int i = 0; i < MONITORS_CNT; i++) { 254 globalLockArray[i] = new Object(); 255 } 256 257 Thread batch[] = new Thread[VT_COUNT]; 258 for (int i = 0; i < VT_COUNT; i++) { 259 batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO4); 260 } 261 262 Thread.sleep(10000); 263 finish = true; 264 265 for (int i = 0; i < VT_COUNT; i++) { 266 batch[i].join(); 267 } 268 269 if (workerCount.get() != VT_COUNT) { 270 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 271 } 272 } 273 274 275 static void recursive5_1(int depth, int lockNumber, Object[] myLockArray) { 276 if (depth > 0) { 277 recursive5_1(depth - 1, lockNumber, myLockArray); 278 } else { 279 if (Math.random() < 0.5) { 280 Thread.yield(); 281 } 282 recursive5_2(lockNumber, myLockArray); 283 } 284 } 285 286 static void recursive5_2(int lockNumber, Object[] myLockArray) { 287 if (lockNumber + 2 <= MONITORS_CNT - 1) { 288 lockNumber += 2; 289 synchronized (myLockArray[lockNumber]) { 290 if (Math.random() < 0.5) { 291 Thread.yield(); 292 } 293 synchronized (globalLockArray[lockNumber]) { 294 Thread.yield(); 295 recursive5_2(lockNumber, myLockArray); 296 } 297 } 298 } 299 } 300 301 static final Runnable FOO5 = () -> { 302 Object[] myLockArray = new Object[MONITORS_CNT]; 303 for (int i = 0; i < MONITORS_CNT; i++) { 304 myLockArray[i] = new Object(); 305 } 306 307 while (!finish) { 308 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 309 synchronized (myLockArray[lockNumber]) { 310 synchronized (globalLockArray[lockNumber]) { 311 recursive5_1(lockNumber, lockNumber, myLockArray); 312 } 313 } 314 } 315 workerCount.getAndIncrement(); 316 System.out.println("Exiting FOO5 from thread " + Thread.currentThread().getName()); 317 }; 318 319 /** 320 * Test contention on monitorenter with extra monitors on stack both local only and shared by all threads. 321 */ 322 @Test 323 void testContentionMultipleMonitors2() throws Exception { 324 final int VT_COUNT = CARRIER_COUNT * 8; 325 workerCount.getAndSet(0); 326 finish = false; 327 328 globalLockArray = new Object[MONITORS_CNT]; 329 for (int i = 0; i < MONITORS_CNT; i++) { 330 globalLockArray[i] = new Object(); 331 } 332 333 // Create batch of VT threads. 334 Thread batch[] = new Thread[VT_COUNT]; 335 for (int i = 0; i < VT_COUNT; i++) { 336 //Thread.ofVirtual().name("FirstBatchVT-" + i).start(FOO); 337 batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO5); 338 } 339 340 Thread.sleep(10000); 341 342 finish = true; 343 344 for (int i = 0; i < VT_COUNT; i++) { 345 batch[i].join(); 346 } 347 348 if (workerCount.get() != VT_COUNT) { 349 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 350 } 351 } 352 353 static synchronized void recursive6(int depth, Object myLock) { 354 if (depth > 0) { 355 recursive6(depth - 1, myLock); 356 } else { 357 if (Math.random() < 0.5) { 358 Thread.yield(); 359 } else { 360 synchronized (myLock) { 361 Thread.yield(); 362 } 363 } 364 } 365 } 366 367 static final Runnable FOO6 = () -> { 368 Object myLock = new Object(); 369 370 while (!finish) { 371 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 372 synchronized (myLock) { 373 synchronized (globalLockArray[lockNumber]) { 374 recursive6(lockNumber, myLock); 375 } 376 } 377 } 378 workerCount.getAndIncrement(); 379 System.out.println("Exiting FOO5 from thread " + Thread.currentThread().getName()); 380 }; 381 382 /** 383 * Test contention on monitorenter with synchronized methods. 384 */ 385 @Test 386 void testContentionMultipleMonitors3() throws Exception { 387 final int VT_COUNT = CARRIER_COUNT * 8; 388 workerCount.getAndSet(0); 389 finish = false; 390 391 392 globalLockArray = new Object[MONITORS_CNT]; 393 for (int i = 0; i < MONITORS_CNT; i++) { 394 globalLockArray[i] = new Object(); 395 } 396 397 // Create batch of VT threads. 398 Thread batch[] = new Thread[VT_COUNT]; 399 for (int i = 0; i < VT_COUNT; i++) { 400 batch[i] = ThreadBuilders.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO6); 401 } 402 403 Thread.sleep(10000); 404 405 finish = true; 406 407 for (int i = 0; i < VT_COUNT; i++) { 408 batch[i].join(); 409 } 410 411 if (workerCount.get() != VT_COUNT) { 412 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 413 } 414 } 415 }