diff --git a/.java-version b/.java-version index 4684374..74623ac 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -1.8 \ No newline at end of file +21.0 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3e3dc5f..249a153 100644 --- a/pom.xml +++ b/pom.xml @@ -165,6 +165,13 @@ ${project.build.outputDirectory} -Xmx1024m -Xms128m ${argLine} alphabetical + + + + + ${project.build.directory}/classes/META-INF/versions/21 + + @@ -312,25 +319,114 @@ false - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - 1.8 - 1.8 - true - ${project.build.sourceEncoding} - true - - -Xlint - - - - \ No newline at end of file + + + java21 + + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + compile-java-8 + + compile + + + 1.8 + 1.8 + true + ${project.build.sourceEncoding} + true + + -Xlint + + + + + compile-java-21 + compile + + compile + + + 21 + + ${project.basedir}/src/main/java21 + + true + true + ${project.build.sourceEncoding} + true + + -Xlint + + + + + test-java-21 + test + + testCompile + + + 21 + + ${project.basedir}/src/test/java21 + + true + ${project.build.sourceEncoding} + true + + -Xlint + + + + + + + + + + + + java8 + + [1.8,21) + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + true + ${project.build.sourceEncoding} + true + + -Xlint + + + + + + + + + diff --git a/src/main/java/com/github/thunkware/ExecutorTool.java b/src/main/java/com/github/thunkware/ExecutorTool.java index 0fc74ba..fe13b1b 100644 --- a/src/main/java/com/github/thunkware/ExecutorTool.java +++ b/src/main/java/com/github/thunkware/ExecutorTool.java @@ -4,7 +4,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; -import static com.github.thunkware.ThreadProvider.ThreadProviderFactory.threadProvider; +import static com.github.thunkware.ThreadProvider.ThreadProviderFactory.getThreadProvider; /** * Utility for working with Executors API from Java 21 in Java 8+ @@ -18,7 +18,7 @@ public class ExecutorTool { * @return true if the JVM supports virtual threads */ public static final boolean hasVirtualThreads() { - return threadProvider.hasVirtualThreads(); + return getThreadProvider().hasVirtualThreads(); } /** @@ -35,7 +35,7 @@ public static final boolean hasVirtualThreads() { * @throws NullPointerException if threadFactory is null */ public static ExecutorService newThreadPerTaskExecutor(ThreadFactory threadFactory) { - return threadProvider.newThreadPerTaskExecutor(threadFactory); + return getThreadProvider().newThreadPerTaskExecutor(threadFactory); } /** @@ -49,7 +49,7 @@ public static ExecutorService newThreadPerTaskExecutor(ThreadFactory threadFacto * @return a new executor that creates a new virtual Thread for each task */ public static ExecutorService newVirtualThreadPerTaskExecutor() { - return threadProvider.newVirtualThreadPerTaskExecutor(); + return getThreadProvider().newVirtualThreadPerTaskExecutor(); } /** @@ -59,7 +59,7 @@ public static ExecutorService newVirtualThreadPerTaskExecutor() { * @return a new executor with limited concurrency */ public static ExecutorService newSempahoreVirtualExecutor(int permits) { - ExecutorService executor = threadProvider.newVirtualThreadPerTaskExecutor(); + ExecutorService executor = getThreadProvider().newVirtualThreadPerTaskExecutor(); return new SempahoreExecutor(executor, permits); } diff --git a/src/main/java/com/github/thunkware/ThreadBuilders8.java b/src/main/java/com/github/thunkware/ThreadBuilders8.java new file mode 100644 index 0000000..56cbe49 --- /dev/null +++ b/src/main/java/com/github/thunkware/ThreadBuilders8.java @@ -0,0 +1,323 @@ +package com.github.thunkware; + +import com.github.thunkware.ThreadTool.Builder.OfPlatform; +import com.github.thunkware.ThreadTool.Builder.OfVirtual; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Defines static methods to create platform and virtual thread builders. + */ +class ThreadBuilders8 { + private ThreadBuilders8() { } + + /** + * Base class for Thread.Builder implementations. + */ + private static class BaseThreadBuilder { + private String name; + private long counter; + private UncaughtExceptionHandler uhe; + + String name() { + return name; + } + + long counter() { + return counter; + } + + UncaughtExceptionHandler uncaughtExceptionHandler() { + return uhe; + } + + String nextThreadName() { + if (name != null && counter >= 0) { + return name + (counter++); + } else { + return name; + } + } + + void setName(String name) { + this.name = Objects.requireNonNull(name); + this.counter = -1; + } + + void setName(String prefix, long start) { + Objects.requireNonNull(prefix); + if (start < 0) + throw new IllegalArgumentException("'start' is negative"); + this.name = prefix; + this.counter = start; + } + + void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { + this.uhe = Objects.requireNonNull(ueh); + } + } + + /** + * ThreadBuilder.OfPlatform implementation. + */ + static final class PlatformThreadBuilder + extends BaseThreadBuilder implements OfPlatform { + private ThreadGroup group; + private boolean daemon; + private boolean daemonChanged; + private int priority; + private long stackSize; + + PlatformThreadBuilder() { + } + + @Override + String nextThreadName() { + String name = super.nextThreadName(); + return (name != null) ? name : ThreadTool.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) { + return this; + } + + @Override + public OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh) { + setUncaughtExceptionHandler(ueh); + return this; + } + + @Override + public OfPlatform group(ThreadGroup group) { + this.group = Objects.requireNonNull(group); + return this; + } + + @Override + public OfPlatform daemon(boolean on) { + daemon = on; + daemonChanged = true; + return this; + } + + @Override + public OfPlatform priority(int priority) { + if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) + throw new IllegalArgumentException(); + this.priority = priority; + return this; + } + + @Override + public OfPlatform stackSize(long stackSize) { + if (stackSize < 0L) + throw new IllegalArgumentException(); + this.stackSize = stackSize; + return this; + } + + @Override + public Thread unstarted(Runnable task) { + Objects.requireNonNull(task); + String name = nextThreadName(); + Thread thread = new Thread(group, task, name, stackSize); + if (daemonChanged) + thread.setDaemon(daemon); + if (priority != 0) + thread.setPriority(priority); + UncaughtExceptionHandler uhe = uncaughtExceptionHandler(); + if (uhe != null) + thread.setUncaughtExceptionHandler(uhe); + return thread; + } + + @Override + public Thread start(Runnable task) { + Thread thread = unstarted(task); + thread.start(); + return thread; + } + + @Override + public ThreadFactory factory() { + return new PlatformThreadFactory(group, name(), counter(), + daemonChanged, daemon, priority, stackSize, uncaughtExceptionHandler()); + } + + } + + /** + * ThreadBuilder.OfVirtual implementation. + */ + static final class VirtualThreadBuilder + extends BaseThreadBuilder implements OfVirtual { + + @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) { + return this; + } + + @Override + public OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh) { + setUncaughtExceptionHandler(ueh); + return this; + } + + @Override + public Thread unstarted(Runnable task) { + Objects.requireNonNull(task); + Thread thread = new Thread(task, nextThreadName()); + UncaughtExceptionHandler uhe = uncaughtExceptionHandler(); + if (uhe != null) + thread.setUncaughtExceptionHandler(uhe); + return thread; + } + + @Override + public Thread start(Runnable task) { + Thread thread = unstarted(task); + thread.start(); + return thread; + } + + @Override + public ThreadFactory factory() { + return new VirtualThreadFactory(name(), counter(), uncaughtExceptionHandler()); + } + } + + /** + * Base ThreadFactory implementation. + */ + private abstract static class BaseThreadFactory implements ThreadFactory { + private final String name; + private final UncaughtExceptionHandler uhe; + + private final boolean hasCounter; + private AtomicLong count = new AtomicLong(); + + BaseThreadFactory(String name, + long start, + UncaughtExceptionHandler uhe) { + this.name = name; + if (name != null && start >= 0) { + this.hasCounter = true; + this.count.set(start); + } else { + this.hasCounter = false; + } + this.uhe = uhe; + } + + UncaughtExceptionHandler uncaughtExceptionHandler() { + return uhe; + } + + String nextThreadName() { + if (hasCounter) { + return name + count.getAndIncrement(); + } else { + return name; + } + } + } + + /** + * ThreadFactory for platform threads. + */ + private static class PlatformThreadFactory extends BaseThreadFactory { + private final ThreadGroup group; + private final boolean daemonChanged; + private final boolean daemon; + private final int priority; + private final long stackSize; + + PlatformThreadFactory(ThreadGroup group, + String name, + long start, + boolean daemonChanged, + boolean daemon, + int priority, + long stackSize, + UncaughtExceptionHandler uhe) { + super(name, start, uhe); + this.group = group; + this.daemonChanged = daemonChanged; + this.daemon = daemon; + this.priority = priority; + this.stackSize = stackSize; + } + + @Override + String nextThreadName() { + String name = super.nextThreadName(); + return (name != null) ? name : ThreadTool.genThreadName(); + } + + @Override + public Thread newThread(Runnable task) { + Objects.requireNonNull(task); + String name = nextThreadName(); + Thread thread = new Thread(group, task, name, stackSize); + if (daemonChanged) + thread.setDaemon(daemon); + if (priority != 0) + thread.setPriority(priority); + UncaughtExceptionHandler uhe = uncaughtExceptionHandler(); + if (uhe != null) + thread.setUncaughtExceptionHandler(uhe); + return thread; + } + } + + /** + * ThreadFactory for virtual threads. + */ + private static class VirtualThreadFactory extends BaseThreadFactory { + + VirtualThreadFactory(String name, long start, UncaughtExceptionHandler uhe) { + super(name, start, uhe); + } + + @Override + public Thread newThread(Runnable task) { + Objects.requireNonNull(task); + String name = nextThreadName(); + Thread thread = new Thread(task, name); + thread.setDaemon(true); + UncaughtExceptionHandler uhe = uncaughtExceptionHandler(); + if (uhe != null) + thread.setUncaughtExceptionHandler(uhe); + return thread; + } + } + +} diff --git a/src/main/java/com/github/thunkware/ThreadProvider.java b/src/main/java/com/github/thunkware/ThreadProvider.java index 630bfba..e91c5f6 100644 --- a/src/main/java/com/github/thunkware/ThreadProvider.java +++ b/src/main/java/com/github/thunkware/ThreadProvider.java @@ -1,5 +1,8 @@ package com.github.thunkware; +import com.github.thunkware.ThreadTool.Builder; + +import java.lang.reflect.Constructor; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; @@ -16,10 +19,19 @@ interface ThreadProvider { public ExecutorService newVirtualThreadPerTaskExecutor(); + public Builder.OfPlatform ofPlatform(); + + public Builder.OfVirtual ofVirtual(); + static class ThreadProviderFactory { - static final ThreadProvider threadProvider; + private static ThreadProvider threadProvider; + + // to avoid ugly ExceptionInInitializerError on error, init in a method, not static initializer + static synchronized ThreadProvider getThreadProvider() { + if (threadProvider != null) { + return threadProvider; + } - static { boolean isJava21; try { Class.forName("java.lang.Thread$Builder$OfPlatform"); @@ -28,9 +40,27 @@ static class ThreadProviderFactory { isJava21 = false; } - threadProvider = isJava21 ? new ThreadProvider21() : new ThreadProvider8(); + try { + threadProvider = createThreadProvider(isJava21); + } catch (Exception e) { + throw new IllegalStateException("Cannot create ThreadProvider", e); + } + + return threadProvider; + } + + private static ThreadProvider createThreadProvider(boolean isJava21) throws Exception { + if (isJava21) { + // even though we setup multi-release jar, for easier development, use reflection to create. Some IDEs + // complains if there are two classes with the same fq name (even though in different release dirs) + Class clazz = Class.forName("com.github.thunkware.ThreadProvider21"); + Constructor constructor = clazz.getDeclaredConstructors()[0]; + return (ThreadProvider) constructor.newInstance(); + } else { + return new ThreadProvider8(); + } } } -} \ No newline at end of file +} diff --git a/src/main/java/com/github/thunkware/ThreadProvider21.java b/src/main/java/com/github/thunkware/ThreadProvider21.java deleted file mode 100644 index 9f56f29..0000000 --- a/src/main/java/com/github/thunkware/ThreadProvider21.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.github.thunkware; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.invoke.MethodType; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -final class ThreadProvider21 implements ThreadProvider { - - private static final MethodHandle isVirtual; - private static final MethodHandle startVirtualThread; - private static final MethodHandle ofVirtual; - private static final MethodHandle unstarted; - private static final MethodHandle newThreadPerTaskExecutor; - private static final MethodHandle newVirtualThreadPerTaskExecutor; - - static { - final Lookup publicLookup = MethodHandles.publicLookup(); - isVirtual = invoke(() -> { - final MethodType methodType = MethodType.methodType(boolean.class); - return publicLookup.findVirtual(Thread.class, "isVirtual", methodType); - }); - - startVirtualThread = invoke(() -> { - final MethodType methodType = MethodType.methodType(Thread.class, Runnable.class); - return publicLookup.findStatic(Thread.class, "startVirtualThread", methodType); - }); - - ofVirtual = invoke(() -> { - final Class ofVirtualClass = Class.forName("java.lang.Thread$Builder$OfVirtual"); - final MethodType methodType = MethodType.methodType(ofVirtualClass); - return publicLookup.findStatic(Thread.class, "ofVirtual", methodType); - }); - - unstarted = invoke(() -> { - final Class ofVirtualClass = Class.forName("java.lang.Thread$Builder$OfVirtual"); - final MethodType methodType = MethodType.methodType(Thread.class, Runnable.class); - return publicLookup.findVirtual(ofVirtualClass, "unstarted", methodType); - }); - - newThreadPerTaskExecutor = invoke(() -> { - final MethodType methodType = MethodType.methodType(ExecutorService.class, ThreadFactory.class); - return publicLookup.findStatic(Executors.class, "newThreadPerTaskExecutor", methodType); - }); - - newVirtualThreadPerTaskExecutor = invoke(() -> { - final MethodType methodType = MethodType.methodType(ExecutorService.class); - return publicLookup.findStatic(Executors.class, "newVirtualThreadPerTaskExecutor", methodType); - }); - - } - - @Override - public boolean hasVirtualThreads() { - return true; - } - - @Override - public final boolean isVirtual(final Thread thread) { - try { - return (boolean) isVirtual.invoke(thread); - } catch (final Throwable e) { - throw rethrow(e); - } - } - - @Override - public Thread startVirtualThread(final Runnable task) { - return invoke(() -> (Thread) startVirtualThread.invoke(task)); - } - - @Override - public Thread unstartedVirtualThread(Runnable task) { - return invoke(() -> { - Object builder = ofVirtual.invoke(); - return (Thread) unstarted.invoke(builder, task); - }); - } - - @Override - public ExecutorService newThreadPerTaskExecutor(ThreadFactory threadFactory) { - return invoke(() -> (ExecutorService) newThreadPerTaskExecutor.invoke(threadFactory)); - } - - @Override - public ExecutorService newVirtualThreadPerTaskExecutor() { - return invoke(() -> (ExecutorService) newVirtualThreadPerTaskExecutor.invoke()); - } - - private static RuntimeException rethrow(final Throwable throwable) { - return sneakyThrow(throwable); - } - - @SuppressWarnings("unchecked") - private static R sneakyThrow(final Throwable throwable) throws T { - throw (T) throwable; - } - - private static T invoke(final ThrowingCallable callable) { - try { - return callable.call(); - } catch (final Throwable e) { - throw new IllegalStateException(e); - } - } - - @FunctionalInterface - private static interface ThrowingCallable { - V call() throws Throwable; - } - -} diff --git a/src/main/java/com/github/thunkware/ThreadProvider8.java b/src/main/java/com/github/thunkware/ThreadProvider8.java index 2711318..19ca26a 100644 --- a/src/main/java/com/github/thunkware/ThreadProvider8.java +++ b/src/main/java/com/github/thunkware/ThreadProvider8.java @@ -1,5 +1,8 @@ package com.github.thunkware; +import com.github.thunkware.ThreadTool.Builder.OfPlatform; +import com.github.thunkware.ThreadTool.Builder.OfVirtual; + import java.util.Collections; import java.util.List; import java.util.Set; @@ -47,6 +50,16 @@ public ExecutorService newVirtualThreadPerTaskExecutor() { }); } + @Override + public OfPlatform ofPlatform() { + return new ThreadBuilders8.PlatformThreadBuilder(); + } + + @Override + public OfVirtual ofVirtual() { + return new ThreadBuilders8.VirtualThreadBuilder(); + } + private static class ThreadPerTaskExecutor extends AbstractExecutorService implements ExecutorService { private final ThreadFactory threadFactory; diff --git a/src/main/java/com/github/thunkware/ThreadTool.java b/src/main/java/com/github/thunkware/ThreadTool.java index 4b60b3b..643ed81 100644 --- a/src/main/java/com/github/thunkware/ThreadTool.java +++ b/src/main/java/com/github/thunkware/ThreadTool.java @@ -1,6 +1,10 @@ package com.github.thunkware; -import static com.github.thunkware.ThreadProvider.ThreadProviderFactory.threadProvider; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.thunkware.ThreadProvider.ThreadProviderFactory.getThreadProvider; /** * Utility for working with Threads API from Java 21 in Java 8+ @@ -14,7 +18,7 @@ public class ThreadTool { * @return true if the JVM supports virtual threads */ public static final boolean hasVirtualThreads() { - return threadProvider.hasVirtualThreads(); + return getThreadProvider().hasVirtualThreads(); } /** @@ -25,7 +29,7 @@ public static final boolean hasVirtualThreads() { * @return {@code true} if the thread is a virtual thread */ public static final boolean isVirtual(Thread thread) { - return threadProvider.isVirtual(thread); + return getThreadProvider().isVirtual(thread); } /** @@ -35,7 +39,7 @@ public static final boolean isVirtual(Thread thread) { * @return {@code true} if this thread is a virtual thread */ public static final boolean isVirtual() { - return threadProvider.isVirtual(Thread.currentThread()); + return getThreadProvider().isVirtual(Thread.currentThread()); } /** @@ -46,7 +50,7 @@ public static final boolean isVirtual() { * @return a new, and started, thread */ public static Thread startVirtualThread(Runnable task) { - return threadProvider.startVirtualThread(task); + return getThreadProvider().startVirtualThread(task); } /** @@ -57,9 +61,188 @@ public static Thread startVirtualThread(Runnable task) { * @return a new, and unstarted, thread */ public static Thread unstartedVirtualThread(Runnable task) { - return threadProvider.unstartedVirtualThread(task); + return getThreadProvider().unstartedVirtualThread(task); + } + + /** + * Returns a builder for creating a platform {@code Thread} or {@code ThreadFactory} + * that creates platform threads. + * + * @return A builder for creating {@code Thread} or {@code ThreadFactory} objects. + */ + public static Builder.OfPlatform ofPlatform() { + return getThreadProvider().ofPlatform(); + } + + /** + * On Java 8+, returns a builder for creating a platform {@code Thread} or {@code ThreadFactory} + * that creates platform threads.

+ * On Java 21+, returns a builder for creating a platform {@code Thread} or {@code ThreadFactory} + * that creates virtual threads.

+ * + * @return A builder for creating {@code Thread} or {@code ThreadFactory} objects. + */ + public static Builder.OfVirtual ofVirtual() { + return getThreadProvider().ofVirtual(); + } + + /** + * A builder for {@link Thread} and {@link ThreadFactory} objects. + */ + public interface Builder { + + /** + * Sets the thread name. + * @param name thread name + * @return this builder + */ + Builder name(String name); + + /** + * Sets the thread name to be the concatenation of a string prefix and + * the string representation of a counter value. + * + * @param prefix thread name prefix + * @param start the starting value of the counter + * @return this builder + * @throws IllegalArgumentException if start is negative + */ + Builder name(String prefix, long start); + + /** + * On Java 8+, this method is a no-op.

+ * On Java 21+, sets whether the thread inherits the initial values of {@linkplain + * InheritableThreadLocal inheritable-thread-local} variables from the + * constructing thread. The default is to inherit. + * + * @param inherit {@code true} to inherit, {@code false} to not inherit + * @return this builder + */ + Builder inheritInheritableThreadLocals(boolean inherit); + + /** + * Sets the uncaught exception handler. + * @param ueh uncaught exception handler + * @return this builder + */ + Builder uncaughtExceptionHandler(UncaughtExceptionHandler ueh); + + /** + * Creates a new {@code Thread} from the current state of the builder to + * run the given task. + * + * @param task the object to run when the thread executes + * @return a new unstarted Thread + */ + Thread unstarted(Runnable task); + + /** + * Creates a new {@code Thread} from the current state of the builder and + * schedules it to execute. + * + * @param task the object to run when the thread executes + * @return a new started Thread + */ + Thread start(Runnable task); + + /** + * Returns a {@code ThreadFactory} to create threads from the current + * state of the builder. The returned thread factory is safe for use by + * multiple concurrent threads. + * + * @return a thread factory to create threads + */ + ThreadFactory factory(); + + /** + * A builder for creating a platform {@link Thread} or {@link ThreadFactory} + * that creates platform threads. + */ + interface OfPlatform extends Builder { + + @Override OfPlatform name(String name); + + /** + * @throws IllegalArgumentException {@inheritDoc} + */ + @Override OfPlatform name(String prefix, long start); + + @Override OfPlatform inheritInheritableThreadLocals(boolean inherit); + @Override OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh); + + /** + * Sets the thread group. + * @param group the thread group + * @return this builder + */ + OfPlatform group(ThreadGroup group); + + /** + * Sets the daemon status. + * @param on {@code true} to create daemon threads + * @return this builder + */ + OfPlatform daemon(boolean on); + + /** + * Sets the daemon status to {@code true}. + * @return this builder + */ + default OfPlatform daemon() { + return daemon(true); + } + + /** + * Sets the thread priority. + * @param priority priority + * @return this builder + * @throws IllegalArgumentException if the priority is less than + * {@link Thread#MIN_PRIORITY} or greater than {@link Thread#MAX_PRIORITY} + */ + OfPlatform priority(int priority); + + /** + * Sets the desired stack size. + * + *

The stack size is the approximate number of bytes of address space + * that the Java virtual machine is to allocate for the thread's stack. The + * effect is highly platform dependent and the Java virtual machine is free + * to treat the {@code stackSize} parameter as a "suggestion". If the value + * is unreasonably low for the platform then a platform specific minimum + * may be used. If the value is unreasonably high then a platform specific + * maximum may be used. A value of zero is always ignored. + * + * @param stackSize the desired stack size + * @return this builder + * @throws IllegalArgumentException if the stack size is negative + */ + OfPlatform stackSize(long stackSize); + } + + /** + * A builder for creating a virtual {@link Thread} or {@link ThreadFactory} + * that creates virtual threads. + */ + interface OfVirtual extends Builder { + + @Override OfVirtual name(String name); + + /** + * @throws IllegalArgumentException {@inheritDoc} + */ + @Override OfVirtual name(String prefix, long start); + + @Override OfVirtual inheritInheritableThreadLocals(boolean inherit); + @Override OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh); + } } + + private static final AtomicInteger threadNumber = new AtomicInteger(); + static String genThreadName() { + return "Thread-" + threadNumber.getAndIncrement(); + } + private ThreadTool() { throw new AssertionError(); } diff --git a/src/main/java21/com/github/thunkware/ThreadBuilders21.java b/src/main/java21/com/github/thunkware/ThreadBuilders21.java new file mode 100644 index 0000000..47ce964 --- /dev/null +++ b/src/main/java21/com/github/thunkware/ThreadBuilders21.java @@ -0,0 +1,131 @@ +package com.github.thunkware; + +import com.github.thunkware.ThreadTool.Builder.OfPlatform; +import com.github.thunkware.ThreadTool.Builder.OfVirtual; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ThreadFactory; + +class ThreadBuilders21 { + private ThreadBuilders21() { + } + + static final class PlatformThreadBuilder implements OfPlatform { + private final java.lang.Thread.Builder.OfPlatform delegate = Thread.ofPlatform(); + + @Override + public Thread unstarted(Runnable task) { + return delegate.unstarted(task); + } + + @Override + public Thread start(Runnable task) { + return delegate.start(task); + } + + @Override + public ThreadFactory factory() { + return delegate.factory(); + } + + @Override + public OfPlatform name(String name) { + delegate.name(name); + return this; + } + + @Override + public OfPlatform name(String prefix, long start) { + delegate.name(prefix, start); + return this; + } + + @Override + public OfPlatform inheritInheritableThreadLocals(boolean inherit) { + delegate.inheritInheritableThreadLocals(inherit); + return this; + } + + @Override + public OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh) { + delegate.uncaughtExceptionHandler(ueh); + return this; + } + + @Override + public OfPlatform group(ThreadGroup group) { + delegate.group(group); + return this; + } + + @Override + public OfPlatform daemon(boolean on) { + delegate.daemon(on); + return this; + } + + @Override + public OfPlatform daemon() { + delegate.daemon(); + return this; + } + + @Override + public OfPlatform priority(int priority) { + delegate.priority(priority); + return this; + } + + @Override + public OfPlatform stackSize(long stackSize) { + delegate.stackSize(stackSize); + return this; + } + + } + + static final class VirtualThreadBuilder implements OfVirtual { + private final java.lang.Thread.Builder.OfVirtual delegate = Thread.ofVirtual(); + + @Override + public Thread unstarted(Runnable task) { + return delegate.unstarted(task); + } + + @Override + public Thread start(Runnable task) { + return delegate.start(task); + } + + @Override + public ThreadFactory factory() { + return delegate.factory(); + } + + @Override + public OfVirtual name(String name) { + delegate.name(name); + return this; + } + + @Override + public OfVirtual name(String prefix, long start) { + delegate.name(prefix, start); + return this; + } + + @Override + public OfVirtual inheritInheritableThreadLocals(boolean inherit) { + delegate.inheritInheritableThreadLocals(inherit); + return this; + } + + @Override + public OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh) { + delegate.uncaughtExceptionHandler(ueh); + return this; + } + + } + +} diff --git a/src/main/java21/com/github/thunkware/ThreadProvider21.java b/src/main/java21/com/github/thunkware/ThreadProvider21.java new file mode 100644 index 0000000..94d56ae --- /dev/null +++ b/src/main/java21/com/github/thunkware/ThreadProvider21.java @@ -0,0 +1,52 @@ +package com.github.thunkware; + +import com.github.thunkware.ThreadTool.Builder.OfPlatform; +import com.github.thunkware.ThreadTool.Builder.OfVirtual; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +final class ThreadProvider21 implements ThreadProvider { + + @Override + public boolean hasVirtualThreads() { + return true; + } + + @Override + public final boolean isVirtual(final Thread thread) { + return thread.isVirtual(); + } + + @Override + public Thread startVirtualThread(final Runnable task) { + return Thread.startVirtualThread(task); + } + + @Override + public Thread unstartedVirtualThread(Runnable task) { + return Thread.ofVirtual().unstarted(task); + } + + @Override + public ExecutorService newThreadPerTaskExecutor(ThreadFactory threadFactory) { + return Executors.newThreadPerTaskExecutor(threadFactory); + } + + @Override + public ExecutorService newVirtualThreadPerTaskExecutor() { + return Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public OfPlatform ofPlatform() { + return new ThreadBuilders21.PlatformThreadBuilder(); + } + + @Override + public OfVirtual ofVirtual() { + return new ThreadBuilders21.VirtualThreadBuilder(); + } + +} diff --git a/src/test/java/com/github/thunkware/ThreadTool21Test.java b/src/test/java/com/github/thunkware/ThreadTool21Test.java index 67593aa..a0a54be 100644 --- a/src/test/java/com/github/thunkware/ThreadTool21Test.java +++ b/src/test/java/com/github/thunkware/ThreadTool21Test.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.JavaVersion.JAVA_20; @@ -53,4 +54,27 @@ void test() { }; assertThat(ThreadTool.unstartedVirtualThread(task).isAlive()).isFalse(); } + + @Test + void testOfPlatform() { + ThreadFactory factory = ThreadTool.ofPlatform() + .daemon(true) + .name("foo") + .factory(); + Thread thread = factory.newThread(Thread::yield); + assertThat(thread.isDaemon()).isTrue(); + assertThat(thread.getName()).isEqualTo("foo"); + assertThat(ThreadTool.isVirtual(thread)).isFalse(); + } + + @Test + void testOfVirtual() { + ThreadFactory factory = ThreadTool.ofVirtual() + .name("foo") + .factory(); + Thread thread = factory.newThread(Thread::yield); + assertThat(thread.isDaemon()).isTrue(); + assertThat(thread.getName()).isEqualTo("foo"); + assertThat(ThreadTool.isVirtual(thread)).isTrue(); + } } diff --git a/src/test/java/com/github/thunkware/ThreadTool8Test.java b/src/test/java/com/github/thunkware/ThreadTool8Test.java index a9ce32a..5c93bc7 100644 --- a/src/test/java/com/github/thunkware/ThreadTool8Test.java +++ b/src/test/java/com/github/thunkware/ThreadTool8Test.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.JavaVersion.JAVA_20; @@ -40,4 +41,27 @@ void test() { }; assertThat(ThreadTool.unstartedVirtualThread(task).isAlive()).isFalse(); } + + @Test + void testOfPlatform() { + ThreadFactory factory = ThreadTool.ofPlatform() + .daemon(true) + .name("foo") + .factory(); + Thread thread = factory.newThread(Thread::yield); + assertThat(thread.isDaemon()).isTrue(); + assertThat(thread.getName()).isEqualTo("foo"); + assertThat(ThreadTool.isVirtual(thread)).isFalse(); + } + + @Test + void testOfVirtual() { + ThreadFactory factory = ThreadTool.ofVirtual() + .name("foo") + .factory(); + Thread thread = factory.newThread(Thread::yield); + assertThat(thread.isDaemon()).isTrue(); + assertThat(thread.getName()).isEqualTo("foo"); + assertThat(ThreadTool.isVirtual(thread)).isFalse(); + } }