8304919: Implementation of Virtual Threads

Reviewed-by: lmesnik, cjplummer, psandoz, mchung, sspitsyn, jpai
This commit is contained in:
Alan Bateman 2023-04-11 05:49:54 +00:00
parent 39398075b7
commit 2586f36120
205 changed files with 1379 additions and 1342 deletions

View file

@ -89,7 +89,6 @@ import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import jdk.internal.vm.annotation.ChangesCurrentThread;
import sun.nio.fs.DefaultFileSystemProvider;
import sun.reflect.annotation.AnnotationType;
import sun.nio.ch.Interruptible;
@ -2567,17 +2566,9 @@ public final class System {
return Thread.currentCarrierThread();
}
@ChangesCurrentThread
public <V> V executeOnCarrierThread(Callable<V> task) throws Exception {
Thread thread = Thread.currentThread();
if (thread.isVirtual()) {
Thread carrier = Thread.currentCarrierThread();
carrier.setCurrentThread(carrier);
try {
return task.call();
} finally {
carrier.setCurrentThread(thread);
}
if (Thread.currentThread() instanceof VirtualThread vthread) {
return vthread.executeOnCarrierThread(task);
} else {
return task.call();
}

View file

@ -38,11 +38,7 @@ import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jdk.internal.event.ThreadSleepEvent;
import jdk.internal.javac.PreviewFeature;
import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.StructureViolationExceptions;
import jdk.internal.misc.TerminatingThreadLocal;
import jdk.internal.misc.Unsafe;
@ -56,6 +52,7 @@ import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import sun.nio.ch.Interruptible;
import sun.security.util.SecurityConstants;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -84,11 +81,11 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
*
* <p> Threads support {@link ThreadLocal} variables. These are variables that are
* local to a thread, meaning a thread can have a copy of a variable that is set to
* a value that is independent of the value set by other threads. Thread also supports
* {@link InheritableThreadLocal} variables that are thread local variables that are
* inherited at Thread creation time from the parent Thread. Thread supports a special
* inheritable thread local for the thread {@linkplain #getContextClassLoader()
* context-class-loader}.
* a value that is independent of the value set by other threads. {@code Thread} also
* supports {@link InheritableThreadLocal} variables that are thread local variables
* that are inherited at thread creation time from the parent {@code Thread}.
* {@code Thread} supports a special inheritable thread local for the thread
* {@linkplain #getContextClassLoader() context-class-loader}.
*
* <h2><a id="platform-threads">Platform threads</a></h2>
* <p> {@code Thread} supports the creation of <i>platform threads</i> that are
@ -279,7 +276,8 @@ public class Thread implements Runnable {
/*
* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals;
/*
@ -450,6 +448,36 @@ public class Thread implements Runnable {
private static native void yield0();
/**
* Called before sleeping to create a jdk.ThreadSleep event.
*/
private static ThreadSleepEvent beforeSleep(long nanos) {
ThreadSleepEvent event = null;
if (ThreadSleepEvent.isTurnedOn()) {
try {
event = new ThreadSleepEvent();
event.time = nanos;
event.begin();
} catch (OutOfMemoryError e) {
event = null;
}
}
return event;
}
/**
* Called after sleeping to commit the jdk.ThreadSleep event.
*/
private static void afterSleep(ThreadSleepEvent event) {
if (event != null) {
try {
event.commit();
} catch (OutOfMemoryError e) {
// ignore
}
}
}
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
@ -472,23 +500,16 @@ public class Thread implements Runnable {
throw new IllegalArgumentException("timeout value is negative");
}
if (currentThread() instanceof VirtualThread vthread) {
long nanos = MILLISECONDS.toNanos(millis);
vthread.sleepNanos(nanos);
return;
}
if (ThreadSleepEvent.isTurnedOn()) {
ThreadSleepEvent event = new ThreadSleepEvent();
try {
event.time = MILLISECONDS.toNanos(millis);
event.begin();
long nanos = MILLISECONDS.toNanos(millis);
ThreadSleepEvent event = beforeSleep(nanos);
try {
if (currentThread() instanceof VirtualThread vthread) {
vthread.sleepNanos(nanos);
} else {
sleep0(millis);
} finally {
event.commit();
}
} else {
sleep0(millis);
} finally {
afterSleep(event);
}
}
@ -525,18 +546,24 @@ public class Thread implements Runnable {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (currentThread() instanceof VirtualThread vthread) {
// total sleep time, in nanoseconds
long totalNanos = MILLISECONDS.toNanos(millis);
totalNanos += Math.min(Long.MAX_VALUE - totalNanos, nanos);
vthread.sleepNanos(totalNanos);
return;
}
// total sleep time, in nanoseconds
long totalNanos = MILLISECONDS.toNanos(millis);
totalNanos += Math.min(Long.MAX_VALUE - totalNanos, nanos);
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
ThreadSleepEvent event = beforeSleep(totalNanos);
try {
if (currentThread() instanceof VirtualThread vthread) {
vthread.sleepNanos(totalNanos);
} else {
// millisecond precision
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
sleep0(millis);
}
} finally {
afterSleep(event);
}
sleep(millis);
}
/**
@ -557,20 +584,25 @@ public class Thread implements Runnable {
*/
public static void sleep(Duration duration) throws InterruptedException {
long nanos = NANOSECONDS.convert(duration); // MAX_VALUE if > 292 years
if (nanos < 0)
return;
if (currentThread() instanceof VirtualThread vthread) {
vthread.sleepNanos(nanos);
if (nanos < 0) {
return;
}
// convert to milliseconds
long millis = MILLISECONDS.convert(nanos, NANOSECONDS);
if (nanos > NANOSECONDS.convert(millis, MILLISECONDS)) {
millis += 1L;
ThreadSleepEvent event = beforeSleep(nanos);
try {
if (currentThread() instanceof VirtualThread vthread) {
vthread.sleepNanos(nanos);
} else {
// millisecond precision
long millis = NANOSECONDS.toMillis(nanos);
if (nanos > MILLISECONDS.toNanos(millis)) {
millis += 1L;
}
sleep0(millis);
}
} finally {
afterSleep(event);
}
sleep(millis);
}
/**
@ -612,13 +644,6 @@ public class Thread implements Runnable {
@IntrinsicCandidate
public static void onSpinWait() {}
/**
* Characteristic value signifying that the thread cannot set values for its
* copy of {@link ThreadLocal thread-locals}.
* See Thread initialization.
*/
static final int NO_THREAD_LOCALS = 1 << 1;
/**
* Characteristic value signifying that initial values for {@link
* InheritableThreadLocal inheritable-thread-locals} are not inherited from
@ -658,8 +683,7 @@ public class Thread implements Runnable {
return parent.getContextClassLoader();
} else {
// skip call to getContextClassLoader
ClassLoader cl = parent.contextClassLoader;
return (isSupportedClassLoader(cl)) ? cl : ClassLoader.getSystemClassLoader();
return parent.contextClassLoader;
}
}
@ -728,23 +752,13 @@ public class Thread implements Runnable {
// thread locals
if (!attached) {
if ((characteristics & NO_THREAD_LOCALS) != 0) {
this.threadLocals = ThreadLocal.ThreadLocalMap.NOT_SUPPORTED;
this.inheritableThreadLocals = ThreadLocal.ThreadLocalMap.NOT_SUPPORTED;
this.contextClassLoader = Constants.NOT_SUPPORTED_CLASSLOADER;
} else if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
ThreadLocal.ThreadLocalMap parentMap = parent.inheritableThreadLocals;
if (parentMap != null
&& parentMap != ThreadLocal.ThreadLocalMap.NOT_SUPPORTED
&& parentMap.size() > 0) {
if (parentMap != null && parentMap.size() > 0) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parentMap);
}
ClassLoader parentLoader = contextClassLoader(parent);
if (VM.isBooted() && !isSupportedClassLoader(parentLoader)) {
// parent does not support thread locals so no CCL to inherit
this.contextClassLoader = ClassLoader.getSystemClassLoader();
} else {
this.contextClassLoader = parentLoader;
if (VM.isBooted()) {
this.contextClassLoader = contextClassLoader(parent);
}
} else if (VM.isBooted()) {
// default CCL to the system class loader when not inheriting
@ -752,7 +766,7 @@ public class Thread implements Runnable {
}
}
// Special value to indicate this is a newly-created Thread
// special value to indicate this is a newly-created Thread
// Note that his must match the declaration in ScopedValue.
this.scopedValueBindings = NEW_THREAD_BINDINGS;
}
@ -770,31 +784,19 @@ public class Thread implements Runnable {
this.inheritedAccessControlContext = Constants.NO_PERMISSIONS_ACC;
// thread locals
if ((characteristics & NO_THREAD_LOCALS) != 0) {
this.threadLocals = ThreadLocal.ThreadLocalMap.NOT_SUPPORTED;
this.inheritableThreadLocals = ThreadLocal.ThreadLocalMap.NOT_SUPPORTED;
this.contextClassLoader = Constants.NOT_SUPPORTED_CLASSLOADER;
} else if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
Thread parent = currentThread();
ThreadLocal.ThreadLocalMap parentMap = parent.inheritableThreadLocals;
if (parentMap != null
&& parentMap != ThreadLocal.ThreadLocalMap.NOT_SUPPORTED
&& parentMap.size() > 0) {
if (parentMap != null && parentMap.size() > 0) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parentMap);
}
ClassLoader parentLoader = contextClassLoader(parent);
if (isSupportedClassLoader(parentLoader)) {
this.contextClassLoader = parentLoader;
} else {
// parent does not support thread locals so no CCL to inherit
this.contextClassLoader = ClassLoader.getSystemClassLoader();
}
this.contextClassLoader = contextClassLoader(parent);
} else {
// default CCL to the system class loader when not inheriting
this.contextClassLoader = ClassLoader.getSystemClassLoader();
}
// Special value to indicate this is a newly-created Thread
// special value to indicate this is a newly-created Thread
this.scopedValueBindings = NEW_THREAD_BINDINGS;
// create a FieldHolder object, needed when bound to an OS thread
@ -837,9 +839,8 @@ public class Thread implements Runnable {
* }
*
* @return A builder for creating {@code Thread} or {@code ThreadFactory} objects.
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public static Builder.OfPlatform ofPlatform() {
return new ThreadBuilders.PlatformThreadBuilder();
}
@ -858,12 +859,9 @@ public class Thread implements Runnable {
* }
*
* @return A builder for creating {@code Thread} or {@code ThreadFactory} objects.
* @throws UnsupportedOperationException if preview features are not enabled
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public static Builder.OfVirtual ofVirtual() {
PreviewFeatures.ensureEnabled();
return new ThreadBuilders.VirtualThreadBuilder();
}
@ -893,14 +891,10 @@ public class Thread implements Runnable {
*
* @see Thread#ofPlatform()
* @see Thread#ofVirtual()
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public sealed interface Builder
permits Builder.OfPlatform,
Builder.OfVirtual,
ThreadBuilders.BaseThreadBuilder {
permits Builder.OfPlatform, Builder.OfVirtual {
/**
* Sets the thread name.
@ -936,37 +930,11 @@ public class Thread implements Runnable {
*/
Builder name(String prefix, long start);
/**
* Sets whether the thread is allowed to set values for its copy of {@linkplain
* ThreadLocal thread-local} variables. The default is to allow. If not allowed,
* then any attempt by the thread to set a value for a thread-local with the
* {@link ThreadLocal#set(Object)} method throws {@code
* UnsupportedOperationException}. Any attempt to set the thread's context
* class loader with {@link Thread#setContextClassLoader(ClassLoader)
* setContextClassLoader} also throws. The {@link ThreadLocal#get()} method
* always returns the {@linkplain ThreadLocal#initialValue() initial-value}
* when thread locals are not allowed.
*
* @apiNote This method is intended for cases where there are a large number of
* threads and where potentially unbounded memory usage due to thread locals is
* a concern. Disallowing a thread to set its copy of thread-local variables
* creates the potential for exceptions at run-time so great care is required
* when the thread is used to invoke arbitrary code.
*
* @param allow {@code true} to allow, {@code false} to disallow
* @return this builder
*/
Builder allowSetThreadLocals(boolean allow);
/**
* Sets whether the thread inherits the initial values of {@linkplain
* InheritableThreadLocal inheritable-thread-local} variables from the
* constructing thread. The default is to inherit.
*
* <p> The initial values of {@code InheritableThreadLocal}s are never inherited
* when {@link #allowSetThreadLocals(boolean)} is used to disallow the thread
* to have its own copy of thread-local variables.
*
* @param inherit {@code true} to inherit, {@code false} to not inherit
* @return this builder
*/
@ -1025,9 +993,8 @@ public class Thread implements Runnable {
* this interface causes a {@code NullPointerException} to be thrown.
*
* @see Thread#ofPlatform()
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
sealed interface OfPlatform extends Builder
permits ThreadBuilders.PlatformThreadBuilder {
@ -1038,7 +1005,6 @@ public class Thread implements Runnable {
*/
@Override OfPlatform name(String prefix, long start);
@Override OfPlatform allowSetThreadLocals(boolean allow);
@Override OfPlatform inheritInheritableThreadLocals(boolean inherit);
@Override OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
@ -1101,9 +1067,8 @@ public class Thread implements Runnable {
* this interface causes a {@code NullPointerException} to be thrown.
*
* @see Thread#ofVirtual()
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
sealed interface OfVirtual extends Builder
permits ThreadBuilders.VirtualThreadBuilder {
@ -1114,7 +1079,6 @@ public class Thread implements Runnable {
*/
@Override OfVirtual name(String prefix, long start);
@Override OfVirtual allowSetThreadLocals(boolean allow);
@Override OfVirtual inheritInheritableThreadLocals(boolean inherit);
@Override OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
}
@ -1532,14 +1496,11 @@ public class Thread implements Runnable {
*
* @param task the object to run when the thread executes
* @return a new, and started, virtual thread
* @throws UnsupportedOperationException if preview features are not enabled
* @see <a href="#inheritance">Inheritance when creating threads</a>
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public static Thread startVirtualThread(Runnable task) {
Objects.requireNonNull(task);
PreviewFeatures.ensureEnabled();
var thread = ThreadBuilders.newVirtualThread(null, null, 0, task);
thread.start();
return thread;
@ -1551,9 +1512,8 @@ public class Thread implements Runnable {
*
* @return {@code true} if this thread is a virtual thread
*
* @since 19
* @since 21
*/
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public final boolean isVirtual() {
return (this instanceof BaseVirtualThread);
}
@ -1588,6 +1548,8 @@ public class Thread implements Runnable {
throw new IllegalThreadStateException();
// bind thread to container
if (this.container != null)
throw new IllegalThreadStateException();
setThreadContainer(container);
// start thread
@ -1662,15 +1624,17 @@ public class Thread implements Runnable {
* a chance to clean up before it actually exits.
*/
private void exit() {
// pop any remaining scopes from the stack, this may block
if (headStackableScopes != null) {
StackableScope.popAll();
}
// notify container that thread is exiting
ThreadContainer container = threadContainer();
if (container != null) {
container.onExit(this);
try {
// pop any remaining scopes from the stack, this may block
if (headStackableScopes != null) {
StackableScope.popAll();
}
} finally {
// notify container that thread is exiting
ThreadContainer container = threadContainer();
if (container != null) {
container.onExit(this);
}
}
try {
@ -2383,8 +2347,6 @@ public class Thread implements Runnable {
ClassLoader cl = this.contextClassLoader;
if (cl == null)
return null;
if (!isSupportedClassLoader(cl))
cl = ClassLoader.getSystemClassLoader();
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
@ -2400,10 +2362,6 @@ public class Thread implements Runnable {
* <p> The context {@code ClassLoader} may be set by the creator of the thread
* for use by code running in this thread when loading classes and resources.
*
* <p> The context {@code ClassLoader} cannot be set when the thread is
* {@linkplain Thread.Builder#allowSetThreadLocals(boolean) not allowed} to have
* its own copy of thread local variables.
*
* <p> If a security manager is present, its {@link
* SecurityManager#checkPermission(java.security.Permission) checkPermission}
* method is invoked with a {@link RuntimePermission RuntimePermission}{@code
@ -2414,9 +2372,6 @@ public class Thread implements Runnable {
* the context ClassLoader for this Thread, or null indicating the
* system class loader (or, failing that, the bootstrap class loader)
*
* @throws UnsupportedOperationException if this thread is not allowed
* to set values for its copy of thread-local variables
*
* @throws SecurityException
* if the current thread cannot set the context ClassLoader
*
@ -2428,27 +2383,9 @@ public class Thread implements Runnable {
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
if (!isSupportedClassLoader(contextClassLoader)) {
throw new UnsupportedOperationException(
"The context class loader cannot be set");
}
contextClassLoader = cl;
}
/**
* Returns true if the given ClassLoader is a "supported" class loader. All
* class loaders, except ClassLoaders.NOT_SUPPORTED, are considered supported.
* This method allows the initialization of ClassLoaders to be delayed until
* it is required.
*/
private static boolean isSupportedClassLoader(ClassLoader loader) {
if (loader == null)
return true;
if (loader == jdk.internal.loader.ClassLoaders.appClassLoader())
return true;
return loader != Constants.NOT_SUPPORTED_CLASSLOADER;
}
/**
* Returns {@code true} if and only if the current thread holds the
* monitor lock on the specified object.
@ -2985,9 +2922,6 @@ public class Thread implements Runnable {
@SuppressWarnings("removal")
static final AccessControlContext NO_PERMISSIONS_ACC;
// Placeholder TCCL when thread locals not supported
static final ClassLoader NOT_SUPPORTED_CLASSLOADER;
static {
var getThreadGroup = new PrivilegedAction<ThreadGroup>() {
@Override
@ -3005,16 +2939,6 @@ public class Thread implements Runnable {
NO_PERMISSIONS_ACC = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, null)
});
var createClassLoader = new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return new ClassLoader(null) { };
}
};
@SuppressWarnings("removal")
ClassLoader loader = AccessController.doPrivileged(createClassLoader);
NOT_SUPPORTED_CLASSLOADER = loader;
}
}
@ -3040,7 +2964,7 @@ public class Thread implements Runnable {
int threadLocalRandomSecondarySeed;
/** The thread container that this thread is in */
private volatile ThreadContainer container; // @Stable candidate?
private @Stable ThreadContainer container;
ThreadContainer threadContainer() {
return container;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,6 @@
*/
package java.lang;
import java.lang.Thread.Builder;
import java.lang.Thread.Builder.OfPlatform;
import java.lang.Thread.Builder.OfVirtual;
import java.lang.Thread.UncaughtExceptionHandler;
@ -41,12 +40,12 @@ import jdk.internal.vm.ContinuationSupport;
* Defines static methods to create platform and virtual thread builders.
*/
class ThreadBuilders {
private ThreadBuilders() { }
/**
* Base implementation of ThreadBuilder.
* Base class for Thread.Builder implementations.
*/
static abstract non-sealed
class BaseThreadBuilder<T extends Builder> implements Builder {
private static class BaseThreadBuilder {
private String name;
private long counter;
private int characteristics;
@ -76,52 +75,29 @@ class ThreadBuilders {
}
}
@Override
@SuppressWarnings("unchecked")
public T name(String name) {
void setName(String name) {
this.name = Objects.requireNonNull(name);
this.counter = -1;
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T name(String prefix, long start) {
void setName(String prefix, long start) {
Objects.requireNonNull(prefix);
if (start < 0)
throw new IllegalArgumentException("'start' is negative");
this.name = prefix;
this.counter = start;
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T allowSetThreadLocals(boolean allow) {
if (allow) {
characteristics &= ~Thread.NO_THREAD_LOCALS;
} else {
characteristics |= Thread.NO_THREAD_LOCALS;
}
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T inheritInheritableThreadLocals(boolean inherit) {
void setInheritInheritableThreadLocals(boolean inherit) {
if (inherit) {
characteristics &= ~Thread.NO_INHERIT_THREAD_LOCALS;
} else {
characteristics |= Thread.NO_INHERIT_THREAD_LOCALS;
}
return (T) this;
}
@Override
@SuppressWarnings("unchecked")
public T uncaughtExceptionHandler(UncaughtExceptionHandler ueh) {
void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) {
this.uhe = Objects.requireNonNull(ueh);
return (T) this;
}
}
@ -129,7 +105,7 @@ class ThreadBuilders {
* ThreadBuilder.OfPlatform implementation.
*/
static final class PlatformThreadBuilder
extends BaseThreadBuilder<OfPlatform> implements OfPlatform {
extends BaseThreadBuilder implements OfPlatform {
private ThreadGroup group;
private boolean daemon;
private boolean daemonChanged;
@ -145,6 +121,30 @@ class ThreadBuilders {
return (name != null) ? name : Thread.genThreadName();
}
@Override
public OfPlatform name(String name) {
setName(name);
return this;
}
@Override
public OfPlatform name(String prefix, long start) {
setName(prefix, start);
return this;
}
@Override
public OfPlatform inheritInheritableThreadLocals(boolean inherit) {
setInheritInheritableThreadLocals(inherit);
return this;
}
@Override
public OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh) {
setUncaughtExceptionHandler(ueh);
return this;
}
@Override
public OfPlatform group(ThreadGroup group) {
this.group = Objects.requireNonNull(group);
@ -208,7 +208,7 @@ class ThreadBuilders {
* ThreadBuilder.OfVirtual implementation.
*/
static final class VirtualThreadBuilder
extends BaseThreadBuilder<OfVirtual> implements OfVirtual {
extends BaseThreadBuilder implements OfVirtual {
private Executor scheduler;
VirtualThreadBuilder() {
@ -221,6 +221,30 @@ class ThreadBuilders {
this.scheduler = Objects.requireNonNull(scheduler);
}
@Override
public OfVirtual name(String name) {
setName(name);
return this;
}
@Override
public OfVirtual name(String prefix, long start) {
setName(prefix, start);
return this;
}
@Override
public OfVirtual inheritInheritableThreadLocals(boolean inherit) {
setInheritInheritableThreadLocals(inherit);
return this;
}
@Override
public OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh) {
setUncaughtExceptionHandler(ueh);
return this;
}
@Override
public Thread unstarted(Runnable task) {
Objects.requireNonNull(task);

View file

@ -29,9 +29,11 @@ import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import jdk.internal.misc.CarrierThreadLocal;
import jdk.internal.misc.TerminatingThreadLocal;
import sun.security.action.GetPropertyAction;
/**
* This class provides thread-local variables. These variables differ from
@ -77,6 +79,8 @@ import jdk.internal.misc.TerminatingThreadLocal;
* @since 1.2
*/
public class ThreadLocal<T> {
private static final boolean TRACE_VTHREAD_LOCALS = traceVirtualThreadLocals();
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
@ -161,11 +165,8 @@ public class ThreadLocal<T> {
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
* If the current thread does not support thread locals then
* this method returns its {@link #initialValue}.
*
* @return the current thread's value of this thread-local
* @see Thread.Builder#allowSetThreadLocals(boolean)
*/
public T get() {
return get(Thread.currentThread());
@ -183,15 +184,11 @@ public class ThreadLocal<T> {
private T get(Thread t) {
ThreadLocalMap map = getMap(t);
if (map != null) {
if (map == ThreadLocalMap.NOT_SUPPORTED) {
return initialValue();
} else {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
return setInitialValue(t);
@ -211,7 +208,7 @@ public class ThreadLocal<T> {
private boolean isPresent(Thread t) {
ThreadLocalMap map = getMap(t);
if (map != null && map != ThreadLocalMap.NOT_SUPPORTED) {
if (map != null) {
return map.getEntry(this) != null;
} else {
return false;
@ -227,7 +224,6 @@ public class ThreadLocal<T> {
private T setInitialValue(Thread t) {
T value = initialValue();
ThreadLocalMap map = getMap(t);
assert map != ThreadLocalMap.NOT_SUPPORTED;
if (map != null) {
map.set(this, value);
} else {
@ -236,6 +232,9 @@ public class ThreadLocal<T> {
if (this instanceof TerminatingThreadLocal<?> ttl) {
TerminatingThreadLocal.register(ttl);
}
if (TRACE_VTHREAD_LOCALS) {
dumpStackIfVirtualThread();
}
return value;
}
@ -247,14 +246,12 @@ public class ThreadLocal<T> {
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*
* @throws UnsupportedOperationException if the current thread is not
* allowed to set its copy of thread-local variables
*
* @see Thread.Builder#allowSetThreadLocals(boolean)
*/
public void set(T value) {
set(Thread.currentThread(), value);
if (TRACE_VTHREAD_LOCALS) {
dumpStackIfVirtualThread();
}
}
void setCarrierThreadLocal(T value) {
@ -264,9 +261,6 @@ public class ThreadLocal<T> {
private void set(Thread t, T value) {
ThreadLocalMap map = getMap(t);
if (map == ThreadLocalMap.NOT_SUPPORTED) {
throw new UnsupportedOperationException();
}
if (map != null) {
map.set(this, value);
} else {
@ -296,7 +290,7 @@ public class ThreadLocal<T> {
private void remove(Thread t) {
ThreadLocalMap m = getMap(t);
if (m != null && m != ThreadLocalMap.NOT_SUPPORTED) {
if (m != null) {
m.remove(this);
}
}
@ -394,9 +388,6 @@ public class ThreadLocal<T> {
}
}
// Placeholder when thread locals not supported
static final ThreadLocalMap NOT_SUPPORTED = new ThreadLocalMap();
/**
* The initial capacity -- MUST be a power of two.
*/
@ -807,4 +798,43 @@ public class ThreadLocal<T> {
}
}
}
/**
* Reads the value of the jdk.traceVirtualThreadLocals property to determine if
* a stack trace should be printed when a virtual thread sets a thread local.
*/
private static boolean traceVirtualThreadLocals() {
String propValue = GetPropertyAction.privilegedGetProperty("jdk.traceVirtualThreadLocals");
return (propValue != null)
&& (propValue.isEmpty() || Boolean.parseBoolean(propValue));
}
/**
* Print a stack trace if the current thread is a virtual thread.
*/
static void dumpStackIfVirtualThread() {
if (Thread.currentThread() instanceof VirtualThread vthread) {
try {
var stack = StackWalkerHolder.STACK_WALKER.walk(s ->
s.skip(1) // skip caller
.collect(Collectors.toList()));
// switch to carrier thread to avoid recursive use of thread-locals
vthread.executeOnCarrierThread(() -> {
System.out.println(vthread);
for (StackWalker.StackFrame frame : stack) {
System.out.format(" %s%n", frame.toStackTraceElement());
}
return null;
});
} catch (Exception e) {
throw new InternalError(e);
}
}
}
private static class StackWalkerHolder {
static final StackWalker STACK_WALKER = StackWalker.getInstance();
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,6 +29,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -40,7 +41,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import jdk.internal.event.ThreadSleepEvent;
import jdk.internal.event.VirtualThreadEndEvent;
import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.internal.event.VirtualThreadStartEvent;
@ -58,6 +58,7 @@ import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.JvmtiMountTransition;
import jdk.internal.vm.annotation.ReservedStackAccess;
import sun.nio.ch.Interruptible;
import sun.security.action.GetPropertyAction;
import static java.util.concurrent.TimeUnit.*;
@ -175,7 +176,7 @@ final class VirtualThread extends BaseVirtualThread {
*/
private static class VThreadContinuation extends Continuation {
VThreadContinuation(VirtualThread vthread, Runnable task) {
super(VTHREAD_SCOPE, () -> vthread.run(task));
super(VTHREAD_SCOPE, wrap(vthread, task));
}
@Override
protected void onPinned(Continuation.Pinned reason) {
@ -184,6 +185,14 @@ final class VirtualThread extends BaseVirtualThread {
PinnedThreadPrinter.printStackTrace(System.out, printAll);
}
}
private static Runnable wrap(VirtualThread vthread, Runnable task) {
return new Runnable() {
@Hidden
public void run() {
vthread.run(task);
}
};
}
}
/**
@ -211,13 +220,13 @@ final class VirtualThread extends BaseVirtualThread {
}
// notify JVMTI before mount
notifyJvmtiMount(true, firstRun);
notifyJvmtiMount(/*hide*/true, firstRun);
try {
cont.run();
} finally {
if (cont.isDone()) {
afterTerminate(/*executed*/ true);
afterTerminate();
} else {
afterYield();
}
@ -291,7 +300,7 @@ final class VirtualThread extends BaseVirtualThread {
// first mount
mount();
notifyJvmtiMount(false, true);
notifyJvmtiMount(/*hide*/false, /*first*/true);
// emit JFR event if enabled
if (VirtualThreadStartEvent.isTurnedOn()) {
@ -319,7 +328,7 @@ final class VirtualThread extends BaseVirtualThread {
} finally {
// last unmount
notifyJvmtiUnmount(true, true);
notifyJvmtiUnmount(/*hide*/true, /*last*/true);
unmount();
// final state
@ -341,6 +350,7 @@ final class VirtualThread extends BaseVirtualThread {
* return, the current thread is the virtual thread.
*/
@ChangesCurrentThread
@ReservedStackAccess
private void mount() {
// sets the carrier thread
Thread carrier = Thread.currentCarrierThread();
@ -367,6 +377,7 @@ final class VirtualThread extends BaseVirtualThread {
* current thread is the current platform thread.
*/
@ChangesCurrentThread
@ReservedStackAccess
private void unmount() {
// set Thread.currentThread() to return the platform thread
Thread carrier = this.carrierThread;
@ -404,22 +415,37 @@ final class VirtualThread extends BaseVirtualThread {
notifyJvmtiHideFrames(false);
}
/**
* Executes the given value returning task on the current carrier thread.
*/
@ChangesCurrentThread
<V> V executeOnCarrierThread(Callable<V> task) throws Exception {
assert Thread.currentThread() == this;
switchToCarrierThread();
try {
return task.call();
} finally {
switchToVirtualThread(this);
}
}
/**
* Unmounts this virtual thread, invokes Continuation.yield, and re-mounts the
* thread when continued. When enabled, JVMTI must be notified from this method.
* @return true if the yield was successful
*/
@Hidden
@ChangesCurrentThread
private boolean yieldContinuation() {
// unmount
notifyJvmtiUnmount(true, false);
notifyJvmtiUnmount(/*hide*/true, /*last*/false);
unmount();
try {
return Continuation.yield(VTHREAD_SCOPE);
} finally {
// re-mount
mount();
notifyJvmtiMount(false, false);
notifyJvmtiMount(/*hide*/false, /*first*/false);
}
}
@ -436,7 +462,7 @@ final class VirtualThread extends BaseVirtualThread {
setState(PARKED);
// notify JVMTI that unmount has completed, thread is parked
notifyJvmtiUnmount(false, false);
notifyJvmtiUnmount(/*hide*/false, /*last*/false);
// may have been unparked while parking
if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) {
@ -452,7 +478,7 @@ final class VirtualThread extends BaseVirtualThread {
setState(RUNNABLE);
// notify JVMTI that unmount has completed, thread is runnable
notifyJvmtiUnmount(false, false);
notifyJvmtiUnmount(/*hide*/false, /*last*/false);
// external submit if there are no tasks in the local task queue
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
@ -463,17 +489,26 @@ final class VirtualThread extends BaseVirtualThread {
}
}
/**
* Invoked after the thread terminates execution. It notifies anyone
* waiting for the thread to terminate.
*/
private void afterTerminate() {
afterTerminate(true, true);
}
/**
* Invoked after the thread terminates (or start failed). This method
* notifies anyone waiting for the thread to terminate.
*
* @param notifyContainer true if its container should be notified
* @param executed true if the thread executed, false if it failed to start
*/
private void afterTerminate(boolean executed) {
private void afterTerminate(boolean notifyContainer, boolean executed) {
assert (state() == TERMINATED) && (carrierThread == null);
if (executed) {
notifyJvmtiUnmount(false, true);
notifyJvmtiUnmount(/*hide*/false, /*last*/true);
}
// notify anyone waiting for this virtual thread to terminate
@ -483,13 +518,13 @@ final class VirtualThread extends BaseVirtualThread {
termination.countDown();
}
if (executed) {
// notify container if thread executed
// notify container
if (notifyContainer) {
threadContainer().onExit(this);
// clear references to thread locals
clearReferences();
}
// clear references to thread locals
clearReferences();
}
/**
@ -506,12 +541,16 @@ final class VirtualThread extends BaseVirtualThread {
}
// bind thread to container
assert threadContainer() == null;
setThreadContainer(container);
// start thread
boolean addedToContainer = false;
boolean started = false;
container.onStart(this); // may throw
try {
container.onStart(this); // may throw
addedToContainer = true;
// scoped values may be inherited
inheritScopedValueBindings(container);
@ -521,8 +560,7 @@ final class VirtualThread extends BaseVirtualThread {
} finally {
if (!started) {
setState(TERMINATED);
container.onExit(this);
afterTerminate(/*executed*/ false);
afterTerminate(addedToContainer, /*executed*/false);
}
}
}
@ -551,14 +589,21 @@ final class VirtualThread extends BaseVirtualThread {
return;
// park the thread
boolean yielded = false;
setState(PARKING);
try {
if (!yieldContinuation()) {
// park on the carrier thread when pinned
parkOnCarrierThread(false, 0);
}
yielded = yieldContinuation(); // may throw
} finally {
assert (Thread.currentThread() == this) && (state() == RUNNING);
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
if (!yielded) {
assert state() == PARKING;
setState(RUNNING);
}
}
// park on the carrier thread when pinned
if (!yielded) {
parkOnCarrierThread(false, 0);
}
}
@ -582,14 +627,17 @@ final class VirtualThread extends BaseVirtualThread {
if (nanos > 0) {
long startTime = System.nanoTime();
boolean yielded;
boolean yielded = false;
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
setState(PARKING);
try {
yielded = yieldContinuation();
yielded = yieldContinuation(); // may throw
} finally {
assert (Thread.currentThread() == this)
&& (state() == RUNNING || state() == PARKING);
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
if (!yielded) {
assert state() == PARKING;
setState(RUNNING);
}
cancel(unparker);
}
@ -611,10 +659,15 @@ final class VirtualThread extends BaseVirtualThread {
* @param nanos the waiting time in nanoseconds
*/
private void parkOnCarrierThread(boolean timed, long nanos) {
assert state() == PARKING;
assert state() == RUNNING;
var pinnedEvent = new VirtualThreadPinnedEvent();
pinnedEvent.begin();
VirtualThreadPinnedEvent event;
try {
event = new VirtualThreadPinnedEvent();
event.begin();
} catch (OutOfMemoryError e) {
event = null;
}
setState(PINNED);
try {
@ -632,7 +685,13 @@ final class VirtualThread extends BaseVirtualThread {
// consume parking permit
setParkPermit(false);
pinnedEvent.commit();
if (event != null) {
try {
event.commit();
} catch (OutOfMemoryError e) {
// ignore
}
}
}
/**
@ -707,41 +766,18 @@ final class VirtualThread extends BaseVirtualThread {
void tryYield() {
assert Thread.currentThread() == this;
setState(YIELDING);
boolean yielded = false;
try {
yieldContinuation();
yielded = yieldContinuation(); // may throw
} finally {
assert Thread.currentThread() == this;
if (state() != RUNNING) {
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
if (!yielded) {
assert state() == YIELDING;
setState(RUNNING);
}
}
}
/**
* Sleep the current virtual thread for the given sleep time.
*
* @param nanos the maximum number of nanoseconds to sleep
* @throws InterruptedException if interrupted while sleeping
*/
void sleepNanos(long nanos) throws InterruptedException {
assert Thread.currentThread() == this;
if (nanos >= 0) {
if (ThreadSleepEvent.isTurnedOn()) {
ThreadSleepEvent event = new ThreadSleepEvent();
try {
event.time = nanos;
event.begin();
doSleepNanos(nanos);
} finally {
event.commit();
}
} else {
doSleepNanos(nanos);
}
}
}
/**
* Sleep the current thread for the given sleep time (in nanoseconds). If
* nanos is 0 then the thread will attempt to yield.
@ -751,9 +787,12 @@ final class VirtualThread extends BaseVirtualThread {
* will consume the parking permit so this method makes available the parking
* permit after the sleep. This may be observed as a spurious, but benign,
* wakeup when the thread subsequently attempts to park.
*
* @param nanos the maximum number of nanoseconds to sleep
* @throws InterruptedException if interrupted while sleeping
*/
private void doSleepNanos(long nanos) throws InterruptedException {
assert nanos >= 0;
void sleepNanos(long nanos) throws InterruptedException {
assert Thread.currentThread() == this && nanos >= 0;
if (getAndClearInterrupt())
throw new InterruptedException();
if (nanos == 0) {