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 * @requires vm.continuations 29 * @modules java.base/java.lang:+open 30 * @run junit/othervm MonitorsTest 31 */ 32 33 /* 34 * @test id=Xint 35 * @library /test/lib 36 * @requires vm.continuations 37 * @modules java.base/java.lang:+open 38 * @run junit/othervm -Xint MonitorsTest 39 */ 40 41 /* 42 * @test id=Xcomp 43 * @library /test/lib 44 * @requires vm.continuations 45 * @modules java.base/java.lang:+open 46 * @run junit/othervm -Xcomp MonitorsTest 47 */ 48 49 /* 50 * @test id=Xcomp-TieredStopAtLevel3 51 * @library /test/lib 52 * @requires vm.continuations 53 * @modules java.base/java.lang:+open 54 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=3 MonitorsTest 55 */ 56 57 /* 58 * @test id=Xcomp-noTieredCompilation 59 * @summary Test virtual threads using synchronized 60 * @library /test/lib 61 * @requires vm.continuations 62 * @modules java.base/java.lang:+open 63 * @run junit/othervm -Xcomp -XX:-TieredCompilation MonitorsTest 64 */ 65 66 /* 67 * @test id=gc 68 * @requires vm.debug == true & vm.continuations 69 * @library /test/lib 70 * @modules java.base/java.lang:+open 71 * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MonitorsTest 72 */ 73 74 import java.util.concurrent.atomic.AtomicInteger; 75 import java.util.concurrent.*; 76 77 import jdk.test.lib.thread.VThreadScheduler; 78 import org.junit.jupiter.api.Test; 79 import static org.junit.jupiter.api.Assertions.*; 80 81 class MonitorsTest { 82 final int CARRIER_COUNT = 8; 83 ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT); 84 85 static final Object globalLock = new Object(); 86 static volatile boolean finish = false; 87 static volatile int counter = 0; 88 89 ///////////////////////////////////////////////////////////////////// 90 //////////////////////////// BASIC TESTS //////////////////////////// 91 ///////////////////////////////////////////////////////////////////// 92 93 static final Runnable FOO = () -> { 94 Object lock = new Object(); 95 synchronized(lock) { 96 while(!finish) { 97 Thread.yield(); 98 } 99 } 100 System.err.println("Exiting FOO from thread " + Thread.currentThread().getName()); 101 }; 102 103 static final Runnable BAR = () -> { 104 synchronized(globalLock) { 105 counter++; 106 } 107 System.err.println("Exiting BAR from thread " + Thread.currentThread().getName()); 108 }; 109 110 /** 111 * Test yield while holding monitor. 112 */ 113 @Test 114 void testBasic() throws Exception { 115 final int VT_COUNT = CARRIER_COUNT; 116 117 // Create first batch of VT threads. 118 Thread firstBatch[] = new Thread[VT_COUNT]; 119 for (int i = 0; i < VT_COUNT; i++) { 120 firstBatch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO); 121 } 122 123 // Give time for all threads to reach Thread.yield 124 Thread.sleep(1000); 125 126 // Create second batch of VT threads. 127 Thread secondBatch[] = new Thread[VT_COUNT]; 128 for (int i = 0; i < VT_COUNT; i++) { 129 secondBatch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR); 130 } 131 132 while(counter != VT_COUNT) {} 133 134 finish = true; 135 136 for (int i = 0; i < VT_COUNT; i++) { 137 firstBatch[i].join(); 138 } 139 for (int i = 0; i < VT_COUNT; i++) { 140 secondBatch[i].join(); 141 } 142 } 143 144 static final Runnable BAR2 = () -> { 145 synchronized(globalLock) { 146 counter++; 147 } 148 recursive2(10); 149 System.err.println("Exiting BAR2 from thread " + Thread.currentThread().getName() + "with counter=" + counter); 150 }; 151 152 static void recursive2(int count) { 153 synchronized(Thread.currentThread()) { 154 if (count > 0) { 155 recursive2(count - 1); 156 } else { 157 synchronized(globalLock) { 158 counter++; 159 Thread.yield(); 160 } 161 } 162 } 163 } 164 165 /** 166 * Test yield while holding monitor with recursive locking. 167 */ 168 @Test 169 void testRecursive() throws Exception { 170 final int VT_COUNT = CARRIER_COUNT; 171 counter = 0; 172 finish = false; 173 174 // Create first batch of VT threads. 175 Thread firstBatch[] = new Thread[VT_COUNT]; 176 for (int i = 0; i < VT_COUNT; i++) { 177 firstBatch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("FirstBatchVT-" + i).start(FOO); 178 } 179 180 // Give time for all threads to reach Thread.yield 181 Thread.sleep(1000); 182 183 // Create second batch of VT threads. 184 Thread secondBatch[] = new Thread[VT_COUNT]; 185 for (int i = 0; i < VT_COUNT; i++) { 186 secondBatch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("SecondBatchVT-" + i).start(BAR2); 187 } 188 189 while(counter != 2*VT_COUNT) {} 190 191 finish = true; 192 193 for (int i = 0; i < VT_COUNT; i++) { 194 firstBatch[i].join(); 195 } 196 for (int i = 0; i < VT_COUNT; i++) { 197 secondBatch[i].join(); 198 } 199 } 200 201 static final Runnable FOO3 = () -> { 202 synchronized(globalLock) { 203 while(!finish) { 204 Thread.yield(); 205 } 206 } 207 System.err.println("Exiting FOO3 from thread " + Thread.currentThread().getName()); 208 }; 209 210 /** 211 * Test contention on monitorenter. 212 */ 213 @Test 214 void testContention() throws Exception { 215 final int VT_COUNT = CARRIER_COUNT * 8; 216 counter = 0; 217 finish = false; 218 219 // Create batch of VT threads. 220 Thread batch[] = new Thread[VT_COUNT]; 221 for (int i = 0; i < VT_COUNT; i++) { 222 batch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO3); 223 } 224 225 // Give time for all threads to reach synchronized(globalLock) 226 Thread.sleep(2000); 227 228 finish = true; 229 230 for (int i = 0; i < VT_COUNT; i++) { 231 batch[i].join(); 232 } 233 } 234 235 ///////////////////////////////////////////////////////////////////// 236 //////////////////////////// MAIN TESTS ///////////////////////////// 237 ///////////////////////////////////////////////////////////////////// 238 239 static final int MONITORS_CNT = 12; 240 static Object[] globalLockArray; 241 static AtomicInteger workerCount = new AtomicInteger(0); 242 243 static void recursive4_1(int depth, int lockNumber) { 244 if (depth > 0) { 245 recursive4_1(depth - 1, lockNumber); 246 } else { 247 if (Math.random() < 0.5) { 248 Thread.yield(); 249 } 250 recursive4_2(lockNumber); 251 } 252 } 253 254 static void recursive4_2(int lockNumber) { 255 if (lockNumber + 2 <= MONITORS_CNT - 1) { 256 lockNumber += 2; 257 synchronized(globalLockArray[lockNumber]) { 258 Thread.yield(); 259 recursive4_2(lockNumber); 260 } 261 } 262 } 263 264 static final Runnable FOO4 = () -> { 265 while (!finish) { 266 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 267 synchronized(globalLockArray[lockNumber]) { 268 recursive4_1(lockNumber, lockNumber); 269 } 270 } 271 workerCount.getAndIncrement(); 272 System.err.println("Exiting FOO4 from thread " + Thread.currentThread().getName()); 273 }; 274 275 /** 276 * Test contention on monitorenter with extra monitors on stack shared by all threads. 277 */ 278 @Test 279 void testContentionMultipleMonitors() throws Exception { 280 final int VT_COUNT = CARRIER_COUNT * 8; 281 workerCount.getAndSet(0); 282 finish = false; 283 284 globalLockArray = new Object[MONITORS_CNT]; 285 for (int i = 0; i < MONITORS_CNT; i++) { 286 globalLockArray[i] = new Object(); 287 } 288 289 Thread batch[] = new Thread[VT_COUNT]; 290 for (int i = 0; i < VT_COUNT; i++) { 291 batch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO4); 292 } 293 294 Thread.sleep(5000); 295 finish = true; 296 297 for (int i = 0; i < VT_COUNT; i++) { 298 batch[i].join(); 299 } 300 301 if (workerCount.get() != VT_COUNT) { 302 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 303 } 304 } 305 306 307 static void recursive5_1(int depth, int lockNumber, Object[] myLockArray) { 308 if (depth > 0) { 309 recursive5_1(depth - 1, lockNumber, myLockArray); 310 } else { 311 if (Math.random() < 0.5) { 312 Thread.yield(); 313 } 314 recursive5_2(lockNumber, myLockArray); 315 } 316 } 317 318 static void recursive5_2(int lockNumber, Object[] myLockArray) { 319 if (lockNumber + 2 <= MONITORS_CNT - 1) { 320 lockNumber += 2; 321 synchronized (myLockArray[lockNumber]) { 322 if (Math.random() < 0.5) { 323 Thread.yield(); 324 } 325 synchronized (globalLockArray[lockNumber]) { 326 Thread.yield(); 327 recursive5_2(lockNumber, myLockArray); 328 } 329 } 330 } 331 } 332 333 static final Runnable FOO5 = () -> { 334 Object[] myLockArray = new Object[MONITORS_CNT]; 335 for (int i = 0; i < MONITORS_CNT; i++) { 336 myLockArray[i] = new Object(); 337 } 338 339 while (!finish) { 340 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 341 synchronized (myLockArray[lockNumber]) { 342 synchronized (globalLockArray[lockNumber]) { 343 recursive5_1(lockNumber, lockNumber, myLockArray); 344 } 345 } 346 } 347 workerCount.getAndIncrement(); 348 System.err.println("Exiting FOO5 from thread " + Thread.currentThread().getName()); 349 }; 350 351 /** 352 * Test contention on monitorenter with extra monitors on stack both local only and shared by all threads. 353 */ 354 @Test 355 void testContentionMultipleMonitors2() throws Exception { 356 final int VT_COUNT = CARRIER_COUNT * 8; 357 workerCount.getAndSet(0); 358 finish = false; 359 360 globalLockArray = new Object[MONITORS_CNT]; 361 for (int i = 0; i < MONITORS_CNT; i++) { 362 globalLockArray[i] = new Object(); 363 } 364 365 // Create batch of VT threads. 366 Thread batch[] = new Thread[VT_COUNT]; 367 for (int i = 0; i < VT_COUNT; i++) { 368 //Thread.ofVirtual().name("FirstBatchVT-" + i).start(FOO); 369 batch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO5); 370 } 371 372 Thread.sleep(5000); 373 374 finish = true; 375 376 for (int i = 0; i < VT_COUNT; i++) { 377 batch[i].join(); 378 } 379 380 if (workerCount.get() != VT_COUNT) { 381 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 382 } 383 } 384 385 static synchronized void recursive6(int depth, Object myLock) { 386 if (depth > 0) { 387 recursive6(depth - 1, myLock); 388 } else { 389 if (Math.random() < 0.5) { 390 Thread.yield(); 391 } else { 392 synchronized (myLock) { 393 Thread.yield(); 394 } 395 } 396 } 397 } 398 399 static final Runnable FOO6 = () -> { 400 Object myLock = new Object(); 401 402 while (!finish) { 403 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITORS_CNT - 1); 404 synchronized (myLock) { 405 synchronized (globalLockArray[lockNumber]) { 406 recursive6(lockNumber, myLock); 407 } 408 } 409 } 410 workerCount.getAndIncrement(); 411 System.err.println("Exiting FOO5 from thread " + Thread.currentThread().getName()); 412 }; 413 414 /** 415 * Test contention on monitorenter with synchronized methods. 416 */ 417 @Test 418 void testContentionMultipleMonitors3() throws Exception { 419 final int VT_COUNT = CARRIER_COUNT * 8; 420 workerCount.getAndSet(0); 421 finish = false; 422 423 424 globalLockArray = new Object[MONITORS_CNT]; 425 for (int i = 0; i < MONITORS_CNT; i++) { 426 globalLockArray[i] = new Object(); 427 } 428 429 // Create batch of VT threads. 430 Thread batch[] = new Thread[VT_COUNT]; 431 for (int i = 0; i < VT_COUNT; i++) { 432 batch[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name("BatchVT-" + i).start(FOO6); 433 } 434 435 Thread.sleep(5000); 436 437 finish = true; 438 439 for (int i = 0; i < VT_COUNT; i++) { 440 batch[i].join(); 441 } 442 443 if (workerCount.get() != VT_COUNT) { 444 throw new RuntimeException("testContentionMultipleMonitors2 failed. Expected " + VT_COUNT + "but found " + workerCount.get()); 445 } 446 } 447 448 @Test 449 void waitNotifyTest() throws Exception { 450 int threadCount = 1000; 451 int waitTime = 50; 452 long start = System.currentTimeMillis(); 453 Thread[] vthread = new Thread[threadCount]; 454 while (System.currentTimeMillis() - start < 5000) { 455 CountDownLatch latchStart = new CountDownLatch(threadCount); 456 CountDownLatch latchFinish = new CountDownLatch(threadCount); 457 Object object = new Object(); 458 for (int i = 0; i < threadCount; i++) { 459 vthread[i] = Thread.ofVirtual().start(() -> { 460 synchronized (object) { 461 try { 462 latchStart.countDown(); 463 object.wait(waitTime); 464 } catch (InterruptedException e) { 465 //do nothing; 466 } 467 } 468 latchFinish.countDown(); 469 }); 470 } 471 try { 472 latchStart.await(); 473 synchronized (object) { 474 object.notifyAll(); 475 } 476 latchFinish.await(); 477 for (int i = 0; i < threadCount; i++) { 478 vthread[i].join(); 479 } 480 } catch (InterruptedException e) { 481 //do nothing; 482 } 483 } 484 } 485 }