11/*
2- * Copyright (c) 2018, 2024 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2018, 2025 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2424 */
2525package java .lang ;
2626
27- import java .util .Arrays ;
2827import java .util .Locale ;
2928import java .util .Objects ;
3029import java .util .concurrent .CountDownLatch ;
3837import java .util .concurrent .ScheduledExecutorService ;
3938import java .util .concurrent .ScheduledThreadPoolExecutor ;
4039import java .util .concurrent .TimeUnit ;
41- import java .util .stream .Stream ;
4240import jdk .internal .event .VirtualThreadEndEvent ;
4341import jdk .internal .event .VirtualThreadStartEvent ;
4442import jdk .internal .event .VirtualThreadSubmitFailedEvent ;
@@ -66,7 +64,6 @@ final class VirtualThread extends BaseVirtualThread {
6664 private static final Unsafe U = Unsafe .getUnsafe ();
6765 private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope ("VirtualThreads" );
6866 private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler ();
69- private static final ScheduledExecutorService [] DELAYED_TASK_SCHEDULERS = createDelayedTaskSchedulers ();
7067
7168 private static final long STATE = U .objectFieldOffset (VirtualThread .class , "state" );
7269 private static final long PARK_PERMIT = U .objectFieldOffset (VirtualThread .class , "parkPermit" );
@@ -193,13 +190,6 @@ static Executor defaultScheduler() {
193190 return DEFAULT_SCHEDULER ;
194191 }
195192
196- /**
197- * Returns a stream of the delayed task schedulers used to support timed operations.
198- */
199- static Stream <ScheduledExecutorService > delayedTaskSchedulers () {
200- return Arrays .stream (DELAYED_TASK_SCHEDULERS );
201- }
202-
203193 /**
204194 * Returns the continuation scope used for virtual threads.
205195 */
@@ -567,8 +557,9 @@ private void afterYield() {
567557 setState (newState = PARKED );
568558 } else {
569559 // schedule unpark
560+ long timeout = this .timeout ;
570561 assert timeout > 0 ;
571- timeoutTask = schedule (this ::unpark , timeout , NANOSECONDS );
562+ timeoutTask = schedule (this ::parkTimeoutExpired , timeout , NANOSECONDS );
572563 setState (newState = TIMED_PARKED );
573564 }
574565
@@ -618,6 +609,7 @@ private void afterYield() {
618609 // the timeout task to coordinate access to the sequence number and to
619610 // ensure the timeout task doesn't execute until the thread has got to
620611 // the TIMED_WAIT state.
612+ long timeout = this .timeout ;
621613 assert timeout > 0 ;
622614 synchronized (timedWaitLock ()) {
623615 byte seqNo = ++timedWaitSeqNo ;
@@ -890,7 +882,19 @@ private void unblock() {
890882 }
891883
892884 /**
893- * Invoked by timer thread when wait timeout for virtual thread has expired.
885+ * Invoked by FJP worker thread or STPE thread when park timeout expires.
886+ */
887+ private void parkTimeoutExpired () {
888+ assert !VirtualThread .currentThread ().isVirtual ();
889+ if (!getAndSetParkPermit (true )
890+ && (state () == TIMED_PARKED )
891+ && compareAndSetState (TIMED_PARKED , UNPARKED )) {
892+ lazySubmitRunContinuation ();
893+ }
894+ }
895+
896+ /**
897+ * Invoked by FJP worker thread or STPE thread when wait timeout expires.
894898 * If the virtual thread is in timed-wait then this method will unblock the thread
895899 * and submit its task so that it continues and attempts to reenter the monitor.
896900 * This method does nothing if the thread has been woken by notify or interrupt.
@@ -913,7 +917,7 @@ private void waitTimeoutExpired(byte seqNo) {
913917 }
914918 }
915919 if (unblocked ) {
916- submitRunContinuation ();
920+ lazySubmitRunContinuation ();
917921 return ;
918922 }
919923 // need to retry when thread is suspended in time-wait
@@ -1444,40 +1448,54 @@ private static ForkJoinPool createDefaultScheduler() {
14441448 /**
14451449 * Schedule a runnable task to run after a delay.
14461450 */
1447- private static Future <?> schedule (Runnable command , long delay , TimeUnit unit ) {
1448- long tid = Thread .currentThread ().threadId ();
1449- int index = (int ) tid & (DELAYED_TASK_SCHEDULERS .length - 1 );
1450- return DELAYED_TASK_SCHEDULERS [index ].schedule (command , delay , unit );
1451+ private Future <?> schedule (Runnable command , long delay , TimeUnit unit ) {
1452+ if (scheduler instanceof ForkJoinPool pool ) {
1453+ return pool .schedule (command , delay , unit );
1454+ } else {
1455+ return DelayedTaskSchedulers .schedule (command , delay , unit );
1456+ }
14511457 }
14521458
14531459 /**
1454- * Creates the ScheduledThreadPoolExecutors used to execute delayed tasks.
1460+ * Supports scheduling a runnable task to run after a delay. It uses a number
1461+ * of ScheduledThreadPoolExecutor instances to reduce contention on the delayed
1462+ * work queue used. This class is used when using a custom scheduler.
14551463 */
1456- private static ScheduledExecutorService [] createDelayedTaskSchedulers () {
1457- String propName = "jdk.virtualThreadScheduler.timerQueues" ;
1458- String propValue = System .getProperty (propName );
1459- int queueCount ;
1460- if (propValue != null ) {
1461- queueCount = Integer .parseInt (propValue );
1462- if (queueCount != Integer .highestOneBit (queueCount )) {
1463- throw new RuntimeException ("Value of " + propName + " must be power of 2" );
1464- }
1465- } else {
1466- int ncpus = Runtime .getRuntime ().availableProcessors ();
1467- queueCount = Math .max (Integer .highestOneBit (ncpus / 4 ), 1 );
1464+ private static class DelayedTaskSchedulers {
1465+ private static final ScheduledExecutorService [] INSTANCE = createDelayedTaskSchedulers ();
1466+
1467+ static Future <?> schedule (Runnable command , long delay , TimeUnit unit ) {
1468+ long tid = Thread .currentThread ().threadId ();
1469+ int index = (int ) tid & (INSTANCE .length - 1 );
1470+ return INSTANCE [index ].schedule (command , delay , unit );
14681471 }
1469- var schedulers = new ScheduledExecutorService [queueCount ];
1470- for (int i = 0 ; i < queueCount ; i ++) {
1471- ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor )
1472- Executors .newScheduledThreadPool (1 , task -> {
1473- Thread t = InnocuousThread .newThread ("VirtualThread-unparker" , task );
1474- t .setDaemon (true );
1475- return t ;
1476- });
1477- stpe .setRemoveOnCancelPolicy (true );
1478- schedulers [i ] = stpe ;
1472+
1473+ private static ScheduledExecutorService [] createDelayedTaskSchedulers () {
1474+ String propName = "jdk.virtualThreadScheduler.timerQueues" ;
1475+ String propValue = System .getProperty (propName );
1476+ int queueCount ;
1477+ if (propValue != null ) {
1478+ queueCount = Integer .parseInt (propValue );
1479+ if (queueCount != Integer .highestOneBit (queueCount )) {
1480+ throw new RuntimeException ("Value of " + propName + " must be power of 2" );
1481+ }
1482+ } else {
1483+ int ncpus = Runtime .getRuntime ().availableProcessors ();
1484+ queueCount = Math .max (Integer .highestOneBit (ncpus / 4 ), 1 );
1485+ }
1486+ var schedulers = new ScheduledExecutorService [queueCount ];
1487+ for (int i = 0 ; i < queueCount ; i ++) {
1488+ ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor )
1489+ Executors .newScheduledThreadPool (1 , task -> {
1490+ Thread t = InnocuousThread .newThread ("VirtualThread-unparker" , task );
1491+ t .setDaemon (true );
1492+ return t ;
1493+ });
1494+ stpe .setRemoveOnCancelPolicy (true );
1495+ schedulers [i ] = stpe ;
1496+ }
1497+ return schedulers ;
14791498 }
1480- return schedulers ;
14811499 }
14821500
14831501 /**
@@ -1514,4 +1532,4 @@ private static void unblockVirtualThreads() {
15141532 unblocker .setDaemon (true );
15151533 unblocker .start ();
15161534 }
1517- }
1535+ }
0 commit comments