1 /* 2 * Copyright (c) 2018, 2026, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.lang; 26 27 import java.util.Locale; 28 import java.util.Objects; 29 import java.util.concurrent.Executor; 30 import java.util.concurrent.Executors; 31 import java.util.concurrent.ForkJoinPool; 32 import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; 33 import java.util.concurrent.ForkJoinTask; 34 import java.util.concurrent.Future; 35 import java.util.concurrent.RejectedExecutionException; 36 import java.util.concurrent.ScheduledExecutorService; 37 import java.util.concurrent.ScheduledThreadPoolExecutor; 38 import java.util.concurrent.TimeUnit; 39 import jdk.internal.event.VirtualThreadEndEvent; 40 import jdk.internal.event.VirtualThreadStartEvent; 41 import jdk.internal.event.VirtualThreadSubmitFailedEvent; 42 import jdk.internal.misc.CarrierThread; 43 import jdk.internal.misc.InnocuousThread; 44 import jdk.internal.misc.Unsafe; 45 import jdk.internal.vm.Continuation; 46 import jdk.internal.vm.ContinuationScope; 47 import jdk.internal.vm.StackableScope; 48 import jdk.internal.vm.ThreadContainer; 49 import jdk.internal.vm.ThreadContainers; 50 import jdk.internal.vm.annotation.ChangesCurrentThread; 51 import jdk.internal.vm.annotation.Hidden; 52 import jdk.internal.vm.annotation.IntrinsicCandidate; 53 import jdk.internal.vm.annotation.JvmtiHideEvents; 54 import jdk.internal.vm.annotation.JvmtiMountTransition; 55 import jdk.internal.vm.annotation.ReservedStackAccess; 56 import sun.nio.ch.Interruptible; 57 import static java.util.concurrent.TimeUnit.*; 58 59 /** 60 * A thread that is scheduled by the Java virtual machine rather than the operating system. 61 */ 62 final class VirtualThread extends BaseVirtualThread { 63 private static final Unsafe U = Unsafe.getUnsafe(); 64 private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads"); 65 private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler(); 66 67 private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state"); 68 private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit"); 69 private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread"); 70 private static final long ON_WAITING_LIST = U.objectFieldOffset(VirtualThread.class, "onWaitingList"); 71 72 // scheduler and continuation 73 private final Executor scheduler; 74 private final Continuation cont; 75 private final Runnable runContinuation; 76 77 // virtual thread state, accessed by VM 78 private volatile int state; 79 80 /* 81 * Virtual thread state transitions: 82 * 83 * NEW -> STARTED // Thread.start, schedule to run 84 * STARTED -> TERMINATED // failed to start 85 * STARTED -> RUNNING // first run 86 * RUNNING -> TERMINATED // done 87 * 88 * RUNNING -> PARKING // Thread parking with LockSupport.park 89 * PARKING -> PARKED // cont.yield successful, parked indefinitely 90 * PARKED -> UNPARKED // unparked, may be scheduled to continue 91 * UNPARKED -> RUNNING // continue execution after park 92 * 93 * PARKING -> RUNNING // cont.yield failed, need to park on carrier 94 * RUNNING -> PINNED // park on carrier 95 * PINNED -> RUNNING // unparked, continue execution on same carrier 96 * 97 * RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos 98 * TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked 99 * TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue 100 * 101 * TIMED_PARKING -> RUNNING // cont.yield failed, need to park on carrier 102 * RUNNING -> TIMED_PINNED // park on carrier 103 * TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier 104 * 105 * RUNNING -> BLOCKING // blocking on monitor enter 106 * BLOCKING -> BLOCKED // blocked on monitor enter 107 * BLOCKED -> UNBLOCKED // unblocked, may be scheduled to continue 108 * UNBLOCKED -> RUNNING // continue execution after blocked on monitor enter 109 * 110 * RUNNING -> WAITING // transitional state during wait on monitor 111 * WAITING -> WAIT // waiting on monitor 112 * WAIT -> BLOCKED // notified, waiting to be unblocked by monitor owner 113 * WAIT -> UNBLOCKED // interrupted 114 * 115 * RUNNING -> TIMED_WAITING // transition state during timed-waiting on monitor 116 * TIMED_WAITING -> TIMED_WAIT // timed-waiting on monitor 117 * TIMED_WAIT -> BLOCKED // notified, waiting to be unblocked by monitor owner 118 * TIMED_WAIT -> UNBLOCKED // timed-out/interrupted 119 * 120 * RUNNING -> YIELDING // Thread.yield 121 * YIELDING -> YIELDED // cont.yield successful, may be scheduled to continue 122 * YIELDING -> RUNNING // cont.yield failed 123 * YIELDED -> RUNNING // continue execution after Thread.yield 124 */ 125 private static final int NEW = 0; 126 private static final int STARTED = 1; 127 private static final int RUNNING = 2; // runnable-mounted 128 129 // untimed and timed parking 130 private static final int PARKING = 3; 131 private static final int PARKED = 4; // unmounted 132 private static final int PINNED = 5; // mounted 133 private static final int TIMED_PARKING = 6; 134 private static final int TIMED_PARKED = 7; // unmounted 135 private static final int TIMED_PINNED = 8; // mounted 136 private static final int UNPARKED = 9; // unmounted but runnable 137 138 // Thread.yield 139 private static final int YIELDING = 10; 140 private static final int YIELDED = 11; // unmounted but runnable 141 142 // monitor enter 143 private static final int BLOCKING = 12; 144 private static final int BLOCKED = 13; // unmounted 145 private static final int UNBLOCKED = 14; // unmounted but runnable 146 147 // monitor wait/timed-wait 148 private static final int WAITING = 15; 149 private static final int WAIT = 16; // waiting in Object.wait 150 private static final int TIMED_WAITING = 17; 151 private static final int TIMED_WAIT = 18; // waiting in timed-Object.wait 152 153 private static final int TERMINATED = 99; // final state 154 155 // parking permit made available by LockSupport.unpark 156 private volatile boolean parkPermit; 157 158 // blocking permit made available by unblocker thread when another thread exits monitor 159 private volatile boolean blockPermit; 160 161 // true when on the list of virtual threads waiting to be unblocked 162 private volatile boolean onWaitingList; 163 164 // next virtual thread on the list of virtual threads waiting to be unblocked 165 private volatile VirtualThread next; 166 167 // notified by Object.notify/notifyAll while waiting in Object.wait 168 private volatile boolean notified; 169 170 // true when waiting in Object.wait, false for VM internal uninterruptible Object.wait 171 private volatile boolean interruptibleWait; 172 173 // timed-wait support 174 private byte timedWaitSeqNo; 175 176 // timeout for timed-park and timed-wait, only accessed on current/carrier thread 177 private long timeout; 178 179 // timer task for timed-park and timed-wait, only accessed on current/carrier thread 180 private Future<?> timeoutTask; 181 182 // carrier thread when mounted, accessed by VM 183 private volatile Thread carrierThread; 184 185 // true to notifyAll after this virtual thread terminates 186 private volatile boolean notifyAllAfterTerminate; 187 188 /** 189 * Returns the default scheduler. 190 */ 191 static Executor defaultScheduler() { 192 return DEFAULT_SCHEDULER; 193 } 194 195 /** 196 * Returns the continuation scope used for virtual threads. 197 */ 198 static ContinuationScope continuationScope() { 199 return VTHREAD_SCOPE; 200 } 201 202 /** 203 * Creates a new {@code VirtualThread} to run the given task with the given 204 * scheduler. If the given scheduler is {@code null} and the current thread 205 * is a platform thread then the newly created virtual thread will use the 206 * default scheduler. If given scheduler is {@code null} and the current 207 * thread is a virtual thread then the current thread's scheduler is used. 208 * 209 * @param scheduler the scheduler or null 210 * @param name thread name 211 * @param characteristics characteristics 212 * @param task the task to execute 213 */ 214 VirtualThread(Executor scheduler, String name, int characteristics, Runnable task) { 215 super(name, characteristics, /*bound*/ false); 216 Objects.requireNonNull(task); 217 218 // choose scheduler if not specified 219 if (scheduler == null) { 220 Thread parent = Thread.currentThread(); 221 if (parent instanceof VirtualThread vparent) { 222 scheduler = vparent.scheduler; 223 } else { 224 scheduler = DEFAULT_SCHEDULER; 225 } 226 } 227 228 this.scheduler = scheduler; 229 this.cont = new VThreadContinuation(this, task); 230 this.runContinuation = this::runContinuation; 231 } 232 233 /** 234 * The continuation that a virtual thread executes. 235 */ 236 private static class VThreadContinuation extends Continuation { 237 VThreadContinuation(VirtualThread vthread, Runnable task) { 238 super(VTHREAD_SCOPE, wrap(vthread, task)); 239 } 240 @Override 241 protected void onPinned(Continuation.Pinned reason) { 242 } 243 private static Runnable wrap(VirtualThread vthread, Runnable task) { 244 return new Runnable() { 245 @Hidden 246 @JvmtiHideEvents 247 public void run() { 248 vthread.endFirstTransition(); 249 try { 250 vthread.run(task); 251 } finally { 252 vthread.startFinalTransition(); 253 } 254 } 255 }; 256 } 257 } 258 259 /** 260 * Runs or continues execution on the current thread. The virtual thread is mounted 261 * on the current thread before the task runs or continues. It unmounts when the 262 * task completes or yields. 263 */ 264 @ChangesCurrentThread // allow mount/unmount to be inlined 265 private void runContinuation() { 266 // the carrier must be a platform thread 267 if (Thread.currentThread().isVirtual()) { 268 throw new WrongThreadException(); 269 } 270 271 // set state to RUNNING 272 int initialState = state(); 273 if (initialState == STARTED || initialState == UNPARKED 274 || initialState == UNBLOCKED || initialState == YIELDED) { 275 // newly started or continue after parking/blocking/Thread.yield 276 if (!compareAndSetState(initialState, RUNNING)) { 277 return; 278 } 279 // consume permit when continuing after parking or blocking. If continue 280 // after a timed-park or timed-wait then the timeout task is cancelled. 281 if (initialState == UNPARKED) { 282 cancelTimeoutTask(); 283 setParkPermit(false); 284 } else if (initialState == UNBLOCKED) { 285 cancelTimeoutTask(); 286 blockPermit = false; 287 } 288 } else { 289 // not runnable 290 return; 291 } 292 293 mount(); 294 try { 295 cont.run(); 296 } finally { 297 unmount(); 298 if (cont.isDone()) { 299 afterDone(); 300 } else { 301 afterYield(); 302 } 303 } 304 } 305 306 /** 307 * Cancel timeout task when continuing after timed-park or timed-wait. 308 * The timeout task may be executing, or may have already completed. 309 */ 310 private void cancelTimeoutTask() { 311 if (timeoutTask != null) { 312 timeoutTask.cancel(false); 313 timeoutTask = null; 314 } 315 } 316 317 /** 318 * Submits the given task to the given executor. If the scheduler is a 319 * ForkJoinPool then the task is first adapted to a ForkJoinTask. 320 */ 321 private void submit(Executor executor, Runnable task) { 322 if (executor instanceof ForkJoinPool pool) { 323 pool.submit(ForkJoinTask.adapt(task)); 324 } else { 325 executor.execute(task); 326 } 327 } 328 329 /** 330 * Submits the runContinuation task to the scheduler. For the default scheduler, 331 * and calling it on a worker thread, the task will be pushed to the local queue, 332 * otherwise it will be pushed to an external submission queue. 333 * @param scheduler the scheduler 334 * @param retryOnOOME true to retry indefinitely if OutOfMemoryError is thrown 335 * @throws RejectedExecutionException 336 */ 337 private void submitRunContinuation(Executor scheduler, boolean retryOnOOME) { 338 boolean done = false; 339 while (!done) { 340 try { 341 // Pin the continuation to prevent the virtual thread from unmounting 342 // when submitting a task. For the default scheduler this ensures that 343 // the carrier doesn't change when pushing a task. For other schedulers 344 // it avoids deadlock that could arise due to carriers and virtual 345 // threads contending for a lock. 346 if (currentThread().isVirtual()) { 347 Continuation.pin(); 348 try { 349 submit(scheduler, runContinuation); 350 } finally { 351 Continuation.unpin(); 352 } 353 } else { 354 submit(scheduler, runContinuation); 355 } 356 done = true; 357 } catch (RejectedExecutionException ree) { 358 submitFailed(ree); 359 throw ree; 360 } catch (OutOfMemoryError e) { 361 if (retryOnOOME) { 362 U.park(false, 100_000_000); // 100ms 363 } else { 364 throw e; 365 } 366 } 367 } 368 } 369 370 /** 371 * Submits the runContinuation task to the given scheduler as an external submit. 372 * If OutOfMemoryError is thrown then the submit will be retried until it succeeds. 373 * @throws RejectedExecutionException 374 * @see ForkJoinPool#externalSubmit(ForkJoinTask) 375 */ 376 private void externalSubmitRunContinuation(ForkJoinPool pool) { 377 assert Thread.currentThread() instanceof CarrierThread; 378 try { 379 pool.externalSubmit(ForkJoinTask.adapt(runContinuation)); 380 } catch (RejectedExecutionException ree) { 381 submitFailed(ree); 382 throw ree; 383 } catch (OutOfMemoryError e) { 384 submitRunContinuation(pool, true); 385 } 386 } 387 388 /** 389 * Submits the runContinuation task to the scheduler. For the default scheduler, 390 * and calling it on a worker thread, the task will be pushed to the local queue, 391 * otherwise it will be pushed to an external submission queue. 392 * If OutOfMemoryError is thrown then the submit will be retried until it succeeds. 393 * @throws RejectedExecutionException 394 */ 395 private void submitRunContinuation() { 396 submitRunContinuation(scheduler, true); 397 } 398 399 /** 400 * Lazy submit the runContinuation task if invoked on a carrier thread and its local 401 * queue is empty. If not empty, or invoked by another thread, then this method works 402 * like submitRunContinuation and just submits the task to the scheduler. 403 * If OutOfMemoryError is thrown then the submit will be retried until it succeeds. 404 * @throws RejectedExecutionException 405 * @see ForkJoinPool#lazySubmit(ForkJoinTask) 406 */ 407 private void lazySubmitRunContinuation() { 408 if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) { 409 ForkJoinPool pool = ct.getPool(); 410 try { 411 pool.lazySubmit(ForkJoinTask.adapt(runContinuation)); 412 } catch (RejectedExecutionException ree) { 413 submitFailed(ree); 414 throw ree; 415 } catch (OutOfMemoryError e) { 416 submitRunContinuation(); 417 } 418 } else { 419 submitRunContinuation(); 420 } 421 } 422 423 /** 424 * Submits the runContinuation task to the scheduler. For the default scheduler, and 425 * calling it a virtual thread that uses the default scheduler, the task will be 426 * pushed to an external submission queue. This method may throw OutOfMemoryError. 427 * @throws RejectedExecutionException 428 * @throws OutOfMemoryError 429 */ 430 private void externalSubmitRunContinuationOrThrow() { 431 if (scheduler == DEFAULT_SCHEDULER && currentCarrierThread() instanceof CarrierThread ct) { 432 try { 433 ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation)); 434 } catch (RejectedExecutionException ree) { 435 submitFailed(ree); 436 throw ree; 437 } 438 } else { 439 submitRunContinuation(scheduler, false); 440 } 441 } 442 443 /** 444 * If enabled, emits a JFR VirtualThreadSubmitFailedEvent. 445 */ 446 private void submitFailed(RejectedExecutionException ree) { 447 var event = new VirtualThreadSubmitFailedEvent(); 448 if (event.isEnabled()) { 449 event.javaThreadId = threadId(); 450 event.exceptionMessage = ree.getMessage(); 451 event.commit(); 452 } 453 } 454 455 /** 456 * Runs a task in the context of this virtual thread. 457 */ 458 private void run(Runnable task) { 459 assert Thread.currentThread() == this && state == RUNNING; 460 461 // emit JFR event if enabled 462 if (VirtualThreadStartEvent.isTurnedOn()) { 463 var event = new VirtualThreadStartEvent(); 464 event.javaThreadId = threadId(); 465 event.commit(); 466 } 467 468 Object bindings = Thread.scopedValueBindings(); 469 try { 470 runWith(bindings, task); 471 } catch (Throwable exc) { 472 dispatchUncaughtException(exc); 473 } finally { 474 // pop any remaining scopes from the stack, this may block 475 StackableScope.popAll(); 476 477 // emit JFR event if enabled 478 if (VirtualThreadEndEvent.isTurnedOn()) { 479 var event = new VirtualThreadEndEvent(); 480 event.javaThreadId = threadId(); 481 event.commit(); 482 } 483 } 484 } 485 486 /** 487 * Mounts this virtual thread onto the current platform thread. On 488 * return, the current thread is the virtual thread. 489 */ 490 @ChangesCurrentThread 491 @ReservedStackAccess 492 private void mount() { 493 startTransition(/*mount*/true); 494 // We assume following volatile accesses provide equivalent 495 // of acquire ordering, otherwise we need U.loadFence() here. 496 497 // sets the carrier thread 498 Thread carrier = Thread.currentCarrierThread(); 499 setCarrierThread(carrier); 500 501 // sync up carrier thread interrupted status if needed 502 if (interrupted) { 503 carrier.setInterrupt(); 504 } else if (carrier.isInterrupted()) { 505 synchronized (interruptLock) { 506 // need to recheck interrupted status 507 if (!interrupted) { 508 carrier.clearInterrupt(); 509 } 510 } 511 } 512 513 // set Thread.currentThread() to return this virtual thread 514 carrier.setCurrentThread(this); 515 } 516 517 /** 518 * Unmounts this virtual thread from the carrier. On return, the 519 * current thread is the current platform thread. 520 */ 521 @ChangesCurrentThread 522 @ReservedStackAccess 523 private void unmount() { 524 assert !Thread.holdsLock(interruptLock); 525 526 // set Thread.currentThread() to return the platform thread 527 Thread carrier = this.carrierThread; 528 carrier.setCurrentThread(carrier); 529 530 // break connection to carrier thread, synchronized with interrupt 531 synchronized (interruptLock) { 532 setCarrierThread(null); 533 } 534 carrier.clearInterrupt(); 535 536 // We assume previous volatile accesses provide equivalent 537 // of release ordering, otherwise we need U.storeFence() here. 538 endTransition(/*mount*/false); 539 } 540 541 /** 542 * Invokes Continuation.yield, notifying JVMTI (if enabled) to hide frames until 543 * the continuation continues. 544 */ 545 @Hidden 546 private boolean yieldContinuation() { 547 startTransition(/*mount*/false); 548 try { 549 return Continuation.yield(VTHREAD_SCOPE); 550 } finally { 551 endTransition(/*mount*/true); 552 } 553 } 554 555 /** 556 * Invoked in the context of the carrier thread after the Continuation yields when 557 * parking, blocking on monitor enter, Object.wait, or Thread.yield. 558 */ 559 private void afterYield() { 560 assert carrierThread == null; 561 562 // re-adjust parallelism if the virtual thread yielded when compensating 563 if (currentThread() instanceof CarrierThread ct) { 564 ct.endBlocking(); 565 } 566 567 int s = state(); 568 569 // LockSupport.park/parkNanos 570 if (s == PARKING || s == TIMED_PARKING) { 571 int newState; 572 if (s == PARKING) { 573 setState(newState = PARKED); 574 } else { 575 // schedule unpark 576 long timeout = this.timeout; 577 assert timeout > 0; 578 timeoutTask = schedule(this::parkTimeoutExpired, timeout, NANOSECONDS); 579 setState(newState = TIMED_PARKED); 580 } 581 582 // may have been unparked while parking 583 if (parkPermit && compareAndSetState(newState, UNPARKED)) { 584 // lazy submit if local queue is empty 585 lazySubmitRunContinuation(); 586 } 587 return; 588 } 589 590 // Thread.yield 591 if (s == YIELDING) { 592 setState(YIELDED); 593 594 // external submit if there are no tasks in the local task queue 595 if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) { 596 externalSubmitRunContinuation(ct.getPool()); 597 } else { 598 submitRunContinuation(); 599 } 600 return; 601 } 602 603 // blocking on monitorenter 604 if (s == BLOCKING) { 605 setState(BLOCKED); 606 607 // may have been unblocked while blocking 608 if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) { 609 // lazy submit if local queue is empty 610 lazySubmitRunContinuation(); 611 } 612 return; 613 } 614 615 // Object.wait 616 if (s == WAITING || s == TIMED_WAITING) { 617 int newState; 618 boolean blocked; 619 boolean interruptible = interruptibleWait; 620 if (s == WAITING) { 621 setState(newState = WAIT); 622 // may have been notified while in transition 623 blocked = notified && compareAndSetState(WAIT, BLOCKED); 624 } else { 625 // For timed-wait, a timeout task is scheduled to execute. The timeout 626 // task will change the thread state to UNBLOCKED and submit the thread 627 // to the scheduler. A sequence number is used to ensure that the timeout 628 // task only unblocks the thread for this timed-wait. We synchronize with 629 // the timeout task to coordinate access to the sequence number and to 630 // ensure the timeout task doesn't execute until the thread has got to 631 // the TIMED_WAIT state. 632 long timeout = this.timeout; 633 assert timeout > 0; 634 synchronized (timedWaitLock()) { 635 byte seqNo = ++timedWaitSeqNo; 636 timeoutTask = schedule(() -> waitTimeoutExpired(seqNo), timeout, MILLISECONDS); 637 setState(newState = TIMED_WAIT); 638 // May have been notified while in transition. This must be done while 639 // holding the monitor to avoid changing the state of a new timed wait call. 640 blocked = notified && compareAndSetState(TIMED_WAIT, BLOCKED); 641 } 642 } 643 644 if (blocked) { 645 // may have been unblocked already 646 if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) { 647 lazySubmitRunContinuation(); 648 } 649 } else { 650 // may have been interrupted while in transition to wait state 651 if (interruptible && interrupted && compareAndSetState(newState, UNBLOCKED)) { 652 lazySubmitRunContinuation(); 653 } 654 } 655 return; 656 } 657 658 assert false; 659 } 660 661 /** 662 * Invoked after the continuation completes. 663 */ 664 private void afterDone() { 665 afterDone(true); 666 } 667 668 /** 669 * Invoked after the continuation completes (or start failed). Sets the thread 670 * state to TERMINATED and notifies anyone waiting for the thread to terminate. 671 * 672 * @param notifyContainer true if its container should be notified 673 */ 674 private void afterDone(boolean notifyContainer) { 675 assert carrierThread == null; 676 setState(TERMINATED); 677 678 // notifyAll to wakeup any threads waiting for this thread to terminate 679 if (notifyAllAfterTerminate) { 680 synchronized (this) { 681 notifyAll(); 682 } 683 } 684 685 // notify container 686 if (notifyContainer) { 687 threadContainer().remove(this); 688 } 689 690 // clear references to thread locals 691 clearReferences(); 692 } 693 694 /** 695 * Schedules this {@code VirtualThread} to execute. 696 * 697 * @throws IllegalStateException if the container is shutdown or closed 698 * @throws IllegalThreadStateException if the thread has already been started 699 * @throws RejectedExecutionException if the scheduler cannot accept a task 700 */ 701 @Override 702 void start(ThreadContainer container) { 703 if (!compareAndSetState(NEW, STARTED)) { 704 throw new IllegalThreadStateException("Already started"); 705 } 706 707 // bind thread to container 708 assert threadContainer() == null; 709 setThreadContainer(container); 710 711 // start thread 712 boolean addedToContainer = false; 713 boolean started = false; 714 try { 715 container.add(this); // may throw 716 addedToContainer = true; 717 718 // scoped values may be inherited 719 inheritScopedValueBindings(container); 720 721 // submit task to run thread, using externalSubmit if possible 722 externalSubmitRunContinuationOrThrow(); 723 started = true; 724 } finally { 725 if (!started) { 726 afterDone(addedToContainer); 727 } 728 } 729 } 730 731 @Override 732 public void start() { 733 start(ThreadContainers.root()); 734 } 735 736 @Override 737 public void run() { 738 // do nothing 739 } 740 741 /** 742 * Invoked by Thread.join before a thread waits for this virtual thread to terminate. 743 */ 744 void beforeJoin() { 745 notifyAllAfterTerminate = true; 746 } 747 748 /** 749 * Parks until unparked or interrupted. If already unparked then the parking 750 * permit is consumed and this method completes immediately (meaning it doesn't 751 * yield). It also completes immediately if the interrupted status is set. 752 */ 753 @Override 754 void park() { 755 assert Thread.currentThread() == this; 756 757 // complete immediately if parking permit available or interrupted 758 if (getAndSetParkPermit(false) || interrupted) 759 return; 760 761 // park the thread 762 boolean yielded = false; 763 setState(PARKING); 764 try { 765 yielded = yieldContinuation(); 766 } catch (OutOfMemoryError e) { 767 // park on carrier 768 } finally { 769 assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING)); 770 if (!yielded) { 771 assert state() == PARKING; 772 setState(RUNNING); 773 } 774 } 775 776 // park on the carrier thread when pinned 777 if (!yielded) { 778 parkOnCarrierThread(false, 0); 779 } 780 } 781 782 /** 783 * Parks up to the given waiting time or until unparked or interrupted. 784 * If already unparked then the parking permit is consumed and this method 785 * completes immediately (meaning it doesn't yield). It also completes immediately 786 * if the interrupted status is set or the waiting time is {@code <= 0}. 787 * 788 * @param nanos the maximum number of nanoseconds to wait. 789 */ 790 @Override 791 void parkNanos(long nanos) { 792 assert Thread.currentThread() == this; 793 794 // complete immediately if parking permit available or interrupted 795 if (getAndSetParkPermit(false) || interrupted) 796 return; 797 798 // park the thread for the waiting time 799 if (nanos > 0) { 800 long startTime = System.nanoTime(); 801 802 // park the thread, afterYield will schedule the thread to unpark 803 boolean yielded = false; 804 timeout = nanos; 805 setState(TIMED_PARKING); 806 try { 807 yielded = yieldContinuation(); 808 } catch (OutOfMemoryError e) { 809 // park on carrier 810 } finally { 811 assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING)); 812 if (!yielded) { 813 assert state() == TIMED_PARKING; 814 setState(RUNNING); 815 } 816 } 817 818 // park on carrier thread for remaining time when pinned (or OOME) 819 if (!yielded) { 820 long remainingNanos = nanos - (System.nanoTime() - startTime); 821 parkOnCarrierThread(true, remainingNanos); 822 } 823 } 824 } 825 826 /** 827 * Parks the current carrier thread up to the given waiting time or until 828 * unparked or interrupted. If the virtual thread is interrupted then the 829 * interrupted status will be propagated to the carrier thread. 830 * @param timed true for a timed park, false for untimed 831 * @param nanos the waiting time in nanoseconds 832 */ 833 private void parkOnCarrierThread(boolean timed, long nanos) { 834 assert state() == RUNNING; 835 836 setState(timed ? TIMED_PINNED : PINNED); 837 try { 838 if (!parkPermit) { 839 if (!timed) { 840 U.park(false, 0); 841 } else if (nanos > 0) { 842 U.park(false, nanos); 843 } 844 } 845 } finally { 846 setState(RUNNING); 847 } 848 849 // consume parking permit 850 setParkPermit(false); 851 852 // JFR jdk.VirtualThreadPinned event 853 postPinnedEvent("LockSupport.park"); 854 } 855 856 /** 857 * Call into VM when pinned to record a JFR jdk.VirtualThreadPinned event. 858 * Recording the event in the VM avoids having JFR event recorded in Java 859 * with the same name, but different ID, to events recorded by the VM. 860 */ 861 @Hidden 862 private static native void postPinnedEvent(String op); 863 864 /** 865 * Re-enables this virtual thread for scheduling. If this virtual thread is parked 866 * then its task is scheduled to continue, otherwise its next call to {@code park} or 867 * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block. 868 * @param lazySubmit to use lazySubmit if possible 869 * @throws RejectedExecutionException if the scheduler cannot accept a task 870 */ 871 private void unpark(boolean lazySubmit) { 872 if (!getAndSetParkPermit(true) && currentThread() != this) { 873 int s = state(); 874 875 // unparked while parked 876 if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) { 877 if (lazySubmit) { 878 lazySubmitRunContinuation(); 879 } else { 880 submitRunContinuation(); 881 } 882 return; 883 } 884 885 // unparked while parked when pinned 886 if (s == PINNED || s == TIMED_PINNED) { 887 // unpark carrier thread when pinned 888 disableSuspendAndPreempt(); 889 try { 890 synchronized (carrierThreadAccessLock()) { 891 Thread carrier = carrierThread; 892 if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) { 893 U.unpark(carrier); 894 } 895 } 896 } finally { 897 enableSuspendAndPreempt(); 898 } 899 return; 900 } 901 } 902 } 903 904 @Override 905 void unpark() { 906 unpark(false); 907 } 908 909 /** 910 * Invoked by unblocker thread to unblock this virtual thread. 911 */ 912 private void unblock() { 913 assert !Thread.currentThread().isVirtual(); 914 blockPermit = true; 915 if (state() == BLOCKED && compareAndSetState(BLOCKED, UNBLOCKED)) { 916 submitRunContinuation(); 917 } 918 } 919 920 /** 921 * Invoked by FJP worker thread or STPE thread when park timeout expires. 922 */ 923 private void parkTimeoutExpired() { 924 assert !VirtualThread.currentThread().isVirtual(); 925 unpark(true); 926 } 927 928 /** 929 * Invoked by FJP worker thread or STPE thread when wait timeout expires. 930 * If the virtual thread is in timed-wait then this method will unblock the thread 931 * and submit its task so that it continues and attempts to reenter the monitor. 932 * This method does nothing if the thread has been woken by notify or interrupt. 933 */ 934 private void waitTimeoutExpired(byte seqNo) { 935 assert !Thread.currentThread().isVirtual(); 936 937 synchronized (timedWaitLock()) { 938 if (seqNo != timedWaitSeqNo) { 939 // this timeout task is for a past timed-wait 940 return; 941 } 942 if (!compareAndSetState(TIMED_WAIT, UNBLOCKED)) { 943 // already notified (or interrupted) 944 return; 945 } 946 } 947 948 lazySubmitRunContinuation(); 949 } 950 951 /** 952 * Attempts to yield the current virtual thread (Thread.yield). 953 */ 954 void tryYield() { 955 assert Thread.currentThread() == this; 956 setState(YIELDING); 957 boolean yielded = false; 958 try { 959 yielded = yieldContinuation(); // may throw 960 } finally { 961 assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING)); 962 if (!yielded) { 963 assert state() == YIELDING; 964 setState(RUNNING); 965 } 966 } 967 } 968 969 /** 970 * Sleep the current thread for the given sleep time (in nanoseconds). If 971 * nanos is 0 then the thread will attempt to yield. 972 * 973 * @implNote This implementation parks the thread for the given sleeping time 974 * and will therefore be observed in PARKED state during the sleep. Parking 975 * will consume the parking permit so this method makes available the parking 976 * permit after the sleep. This may be observed as a spurious, but benign, 977 * wakeup when the thread subsequently attempts to park. 978 * 979 * @param nanos the maximum number of nanoseconds to sleep 980 * @throws InterruptedException if interrupted while sleeping 981 */ 982 void sleepNanos(long nanos) throws InterruptedException { 983 assert Thread.currentThread() == this && nanos >= 0; 984 if (getAndClearInterrupt()) 985 throw new InterruptedException(); 986 if (nanos == 0) { 987 tryYield(); 988 } else { 989 // park for the sleep time 990 try { 991 long remainingNanos = nanos; 992 long startNanos = System.nanoTime(); 993 while (remainingNanos > 0) { 994 parkNanos(remainingNanos); 995 if (getAndClearInterrupt()) { 996 throw new InterruptedException(); 997 } 998 remainingNanos = nanos - (System.nanoTime() - startNanos); 999 } 1000 } finally { 1001 // may have been unparked while sleeping 1002 setParkPermit(true); 1003 } 1004 } 1005 } 1006 1007 @Override 1008 void blockedOn(Interruptible b) { 1009 disableSuspendAndPreempt(); 1010 try { 1011 super.blockedOn(b); 1012 } finally { 1013 enableSuspendAndPreempt(); 1014 } 1015 } 1016 1017 @Override 1018 public void interrupt() { 1019 if (Thread.currentThread() != this) { 1020 // if current thread is a virtual thread then prevent it from being 1021 // suspended or unmounted when entering or holding interruptLock 1022 Interruptible blocker; 1023 disableSuspendAndPreempt(); 1024 try { 1025 synchronized (interruptLock) { 1026 interrupted = true; 1027 blocker = nioBlocker(); 1028 if (blocker != null) { 1029 blocker.interrupt(this); 1030 } 1031 1032 // interrupt carrier thread if mounted 1033 Thread carrier = carrierThread; 1034 if (carrier != null) carrier.setInterrupt(); 1035 } 1036 } finally { 1037 enableSuspendAndPreempt(); 1038 } 1039 1040 // notify blocker after releasing interruptLock 1041 if (blocker != null) { 1042 blocker.postInterrupt(); 1043 } 1044 1045 // make available parking permit, unpark thread if parked 1046 unpark(); 1047 1048 // if thread is waiting in Object.wait then schedule to try to reenter 1049 int s = state(); 1050 if ((s == WAIT || s == TIMED_WAIT) && compareAndSetState(s, UNBLOCKED)) { 1051 submitRunContinuation(); 1052 } 1053 1054 } else { 1055 interrupted = true; 1056 carrierThread.setInterrupt(); 1057 setParkPermit(true); 1058 } 1059 } 1060 1061 @Override 1062 public boolean isInterrupted() { 1063 return interrupted; 1064 } 1065 1066 @Override 1067 boolean getAndClearInterrupt() { 1068 assert Thread.currentThread() == this; 1069 boolean oldValue = interrupted; 1070 if (oldValue) { 1071 disableSuspendAndPreempt(); 1072 try { 1073 synchronized (interruptLock) { 1074 interrupted = false; 1075 carrierThread.clearInterrupt(); 1076 } 1077 } finally { 1078 enableSuspendAndPreempt(); 1079 } 1080 } 1081 return oldValue; 1082 } 1083 1084 @Override 1085 Thread.State threadState() { 1086 switch (state()) { 1087 case NEW: 1088 return Thread.State.NEW; 1089 case STARTED: 1090 // return NEW if thread container not yet set 1091 if (threadContainer() == null) { 1092 return Thread.State.NEW; 1093 } else { 1094 return Thread.State.RUNNABLE; 1095 } 1096 case UNPARKED: 1097 case UNBLOCKED: 1098 case YIELDED: 1099 // runnable, not mounted 1100 return Thread.State.RUNNABLE; 1101 case RUNNING: 1102 // if mounted then return state of carrier thread 1103 if (Thread.currentThread() != this) { 1104 disableSuspendAndPreempt(); 1105 try { 1106 synchronized (carrierThreadAccessLock()) { 1107 Thread carrierThread = this.carrierThread; 1108 if (carrierThread != null) { 1109 return carrierThread.threadState(); 1110 } 1111 } 1112 } finally { 1113 enableSuspendAndPreempt(); 1114 } 1115 } 1116 // runnable, mounted 1117 return Thread.State.RUNNABLE; 1118 case PARKING: 1119 case TIMED_PARKING: 1120 case WAITING: 1121 case TIMED_WAITING: 1122 case YIELDING: 1123 // runnable, in transition 1124 return Thread.State.RUNNABLE; 1125 case PARKED: 1126 case PINNED: 1127 case WAIT: 1128 return Thread.State.WAITING; 1129 case TIMED_PARKED: 1130 case TIMED_PINNED: 1131 case TIMED_WAIT: 1132 return Thread.State.TIMED_WAITING; 1133 case BLOCKING: 1134 case BLOCKED: 1135 return Thread.State.BLOCKED; 1136 case TERMINATED: 1137 return Thread.State.TERMINATED; 1138 default: 1139 throw new InternalError(); 1140 } 1141 } 1142 1143 @Override 1144 boolean alive() { 1145 int s = state; 1146 return (s != NEW && s != TERMINATED); 1147 } 1148 1149 @Override 1150 boolean isTerminated() { 1151 return (state == TERMINATED); 1152 } 1153 1154 @Override 1155 public String toString() { 1156 StringBuilder sb = new StringBuilder("VirtualThread[#"); 1157 sb.append(threadId()); 1158 String name = getName(); 1159 if (!name.isEmpty()) { 1160 sb.append(","); 1161 sb.append(name); 1162 } 1163 sb.append("]/"); 1164 1165 // add the carrier state and thread name when mounted 1166 boolean mounted; 1167 if (Thread.currentThread() == this) { 1168 mounted = appendCarrierInfo(sb); 1169 } else { 1170 disableSuspendAndPreempt(); 1171 try { 1172 synchronized (carrierThreadAccessLock()) { 1173 mounted = appendCarrierInfo(sb); 1174 } 1175 } finally { 1176 enableSuspendAndPreempt(); 1177 } 1178 } 1179 1180 // add virtual thread state when not mounted 1181 if (!mounted) { 1182 String stateAsString = threadState().toString(); 1183 sb.append(stateAsString.toLowerCase(Locale.ROOT)); 1184 } 1185 1186 return sb.toString(); 1187 } 1188 1189 /** 1190 * Appends the carrier state and thread name to the string buffer if mounted. 1191 * @return true if mounted, false if not mounted 1192 */ 1193 private boolean appendCarrierInfo(StringBuilder sb) { 1194 assert Thread.currentThread() == this || Thread.holdsLock(carrierThreadAccessLock()); 1195 Thread carrier = carrierThread; 1196 if (carrier != null) { 1197 String stateAsString = carrier.threadState().toString(); 1198 sb.append(stateAsString.toLowerCase(Locale.ROOT)); 1199 sb.append('@'); 1200 sb.append(carrier.getName()); 1201 return true; 1202 } else { 1203 return false; 1204 } 1205 } 1206 1207 @Override 1208 public int hashCode() { 1209 return (int) threadId(); 1210 } 1211 1212 @Override 1213 public boolean equals(Object obj) { 1214 return obj == this; 1215 } 1216 1217 /** 1218 * Returns the lock object to synchronize on when accessing carrierThread. 1219 * The lock prevents carrierThread from being reset to null during unmount. 1220 */ 1221 private Object carrierThreadAccessLock() { 1222 // return interruptLock as unmount has to coordinate with interrupt 1223 return interruptLock; 1224 } 1225 1226 /** 1227 * Returns a lock object for coordinating timed-wait setup and timeout handling. 1228 */ 1229 private Object timedWaitLock() { 1230 // use this object for now to avoid the overhead of introducing another lock 1231 return runContinuation; 1232 } 1233 1234 /** 1235 * Disallow the current thread be suspended or preempted. 1236 */ 1237 private void disableSuspendAndPreempt() { 1238 notifyJvmtiDisableSuspend(true); 1239 Continuation.pin(); 1240 } 1241 1242 /** 1243 * Allow the current thread be suspended or preempted. 1244 */ 1245 private void enableSuspendAndPreempt() { 1246 Continuation.unpin(); 1247 notifyJvmtiDisableSuspend(false); 1248 } 1249 1250 // -- wrappers for get/set of state, parking permit, and carrier thread -- 1251 1252 private int state() { 1253 return state; // volatile read 1254 } 1255 1256 private void setState(int newValue) { 1257 state = newValue; // volatile write 1258 } 1259 1260 private boolean compareAndSetState(int expectedValue, int newValue) { 1261 return U.compareAndSetInt(this, STATE, expectedValue, newValue); 1262 } 1263 1264 private boolean compareAndSetOnWaitingList(boolean expectedValue, boolean newValue) { 1265 return U.compareAndSetBoolean(this, ON_WAITING_LIST, expectedValue, newValue); 1266 } 1267 1268 private void setParkPermit(boolean newValue) { 1269 if (parkPermit != newValue) { 1270 parkPermit = newValue; 1271 } 1272 } 1273 1274 private boolean getAndSetParkPermit(boolean newValue) { 1275 if (parkPermit != newValue) { 1276 return U.getAndSetBoolean(this, PARK_PERMIT, newValue); 1277 } else { 1278 return newValue; 1279 } 1280 } 1281 1282 private void setCarrierThread(Thread carrier) { 1283 // U.putReferenceRelease(this, CARRIER_THREAD, carrier); 1284 this.carrierThread = carrier; 1285 } 1286 1287 // The following four methods notify the VM when a "transition" starts and ends. 1288 // A "mount transition" embodies the steps to transfer control from a platform 1289 // thread to a virtual thread, changing the thread identity, and starting or 1290 // resuming the virtual thread's continuation on the carrier. 1291 // An "unmount transition" embodies the steps to transfer control from a virtual 1292 // thread to its carrier, suspending the virtual thread's continuation, and 1293 // restoring the thread identity to the platform thread. 1294 // The notifications to the VM are necessary in order to coordinate with functions 1295 // (JVMTI mostly) that disable transitions for one or all virtual threads. Starting 1296 // a transition may block if transitions are disabled. Ending a transition may 1297 // notify a thread that is waiting to disable transitions. The notifications are 1298 // also used to post JVMTI events for virtual thread start and end. 1299 1300 @IntrinsicCandidate 1301 @JvmtiMountTransition 1302 private native void endFirstTransition(); 1303 1304 @IntrinsicCandidate 1305 @JvmtiMountTransition 1306 private native void startFinalTransition(); 1307 1308 @IntrinsicCandidate 1309 @JvmtiMountTransition 1310 private native void startTransition(boolean mount); 1311 1312 @IntrinsicCandidate 1313 @JvmtiMountTransition 1314 private native void endTransition(boolean mount); 1315 1316 @IntrinsicCandidate 1317 private static native void notifyJvmtiDisableSuspend(boolean enter); 1318 1319 private static native void registerNatives(); 1320 static { 1321 registerNatives(); 1322 1323 // ensure VTHREAD_GROUP is created, may be accessed by JVMTI 1324 var group = Thread.virtualThreadGroup(); 1325 } 1326 1327 /** 1328 * Creates the default ForkJoinPool scheduler. 1329 */ 1330 private static ForkJoinPool createDefaultScheduler() { 1331 ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool); 1332 int parallelism, maxPoolSize, minRunnable; 1333 String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism"); 1334 String maxPoolSizeValue = System.getProperty("jdk.virtualThreadScheduler.maxPoolSize"); 1335 String minRunnableValue = System.getProperty("jdk.virtualThreadScheduler.minRunnable"); 1336 if (parallelismValue != null) { 1337 parallelism = Integer.parseInt(parallelismValue); 1338 } else { 1339 parallelism = Runtime.getRuntime().availableProcessors(); 1340 } 1341 if (maxPoolSizeValue != null) { 1342 maxPoolSize = Integer.parseInt(maxPoolSizeValue); 1343 parallelism = Integer.min(parallelism, maxPoolSize); 1344 } else { 1345 maxPoolSize = Integer.max(parallelism, 256); 1346 } 1347 if (minRunnableValue != null) { 1348 minRunnable = Integer.parseInt(minRunnableValue); 1349 } else { 1350 minRunnable = Integer.max(parallelism / 2, 1); 1351 } 1352 Thread.UncaughtExceptionHandler handler = (t, e) -> { }; 1353 boolean asyncMode = true; // FIFO 1354 return new ForkJoinPool(parallelism, factory, handler, asyncMode, 1355 0, maxPoolSize, minRunnable, pool -> true, 30, SECONDS); 1356 } 1357 1358 /** 1359 * Schedule a runnable task to run after a delay. 1360 */ 1361 private Future<?> schedule(Runnable command, long delay, TimeUnit unit) { 1362 if (scheduler instanceof ForkJoinPool pool) { 1363 return pool.schedule(command, delay, unit); 1364 } else { 1365 return DelayedTaskSchedulers.schedule(command, delay, unit); 1366 } 1367 } 1368 1369 /** 1370 * Supports scheduling a runnable task to run after a delay. It uses a number 1371 * of ScheduledThreadPoolExecutor instances to reduce contention on the delayed 1372 * work queue used. This class is used when using a custom scheduler. 1373 */ 1374 private static class DelayedTaskSchedulers { 1375 private static final ScheduledExecutorService[] INSTANCE = createDelayedTaskSchedulers(); 1376 1377 static Future<?> schedule(Runnable command, long delay, TimeUnit unit) { 1378 long tid = Thread.currentThread().threadId(); 1379 int index = (int) tid & (INSTANCE.length - 1); 1380 return INSTANCE[index].schedule(command, delay, unit); 1381 } 1382 1383 private static ScheduledExecutorService[] createDelayedTaskSchedulers() { 1384 String propName = "jdk.virtualThreadScheduler.timerQueues"; 1385 String propValue = System.getProperty(propName); 1386 int queueCount; 1387 if (propValue != null) { 1388 queueCount = Integer.parseInt(propValue); 1389 if (queueCount != Integer.highestOneBit(queueCount)) { 1390 throw new RuntimeException("Value of " + propName + " must be power of 2"); 1391 } 1392 } else { 1393 int ncpus = Runtime.getRuntime().availableProcessors(); 1394 queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1); 1395 } 1396 var schedulers = new ScheduledExecutorService[queueCount]; 1397 for (int i = 0; i < queueCount; i++) { 1398 ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor) 1399 Executors.newScheduledThreadPool(1, task -> { 1400 Thread t = InnocuousThread.newThread("VirtualThread-unparker", task); 1401 t.setDaemon(true); 1402 return t; 1403 }); 1404 stpe.setRemoveOnCancelPolicy(true); 1405 schedulers[i] = stpe; 1406 } 1407 return schedulers; 1408 } 1409 } 1410 1411 /** 1412 * Schedule virtual threads that are ready to be scheduled after they blocked on 1413 * monitor enter. 1414 */ 1415 private static void unblockVirtualThreads() { 1416 while (true) { 1417 VirtualThread vthread = takeVirtualThreadListToUnblock(); 1418 while (vthread != null) { 1419 assert vthread.onWaitingList; 1420 VirtualThread nextThread = vthread.next; 1421 1422 // remove from list and unblock 1423 vthread.next = null; 1424 boolean changed = vthread.compareAndSetOnWaitingList(true, false); 1425 assert changed; 1426 vthread.unblock(); 1427 1428 vthread = nextThread; 1429 } 1430 } 1431 } 1432 1433 /** 1434 * Retrieves the list of virtual threads that are waiting to be unblocked, waiting 1435 * if necessary until a list of one or more threads becomes available. 1436 */ 1437 private static native VirtualThread takeVirtualThreadListToUnblock(); 1438 1439 static { 1440 var unblocker = InnocuousThread.newThread("VirtualThread-unblocker", 1441 VirtualThread::unblockVirtualThreads); 1442 unblocker.setDaemon(true); 1443 unblocker.start(); 1444 } 1445 } --- EOF ---