diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java index d51f001a6..c9828c9e4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java @@ -18,48 +18,81 @@ package org.jackhuang.hmcl.task; import javafx.application.Platform; +import org.jackhuang.hmcl.util.Lang; +import org.jetbrains.annotations.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.concurrent.*; +import java.util.function.Function; -import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -/** - * - * @author huangyuhui - */ +/// @author huangyuhui public final class Schedulers { private Schedulers() { } - private static volatile ExecutorService IO_EXECUTOR; + private static final @Nullable Function NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR; - /** - * Get singleton instance of the thread pool for I/O operations, - * usually for reading files from disk, or Internet connections. - * - * This thread pool has no more than 4 threads, and number of threads will get - * reduced if concurrency is less than thread number. - * - * @return Thread pool for I/O operations. - */ - public static ExecutorService io() { - if (IO_EXECUTOR == null) { - synchronized (Schedulers.class) { - if (IO_EXECUTOR == null) { - IO_EXECUTOR = threadPool("IO", true, 4, 10, TimeUnit.SECONDS); - } + static { + if (Runtime.version().feature() >= 21) { + try { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + + Class vtBuilderCls = Class.forName("java.lang.Thread$Builder$OfVirtual"); + + MethodHandle ofVirtualHandle = lookup.findStatic(Thread.class, "ofVirtual", MethodType.methodType(vtBuilderCls)); + MethodHandle setNameHandle = lookup.findVirtual(vtBuilderCls, "name", MethodType.methodType(vtBuilderCls, String.class, long.class)); + MethodHandle toFactoryHandle = lookup.findVirtual(vtBuilderCls, "factory", MethodType.methodType(ThreadFactory.class)); + MethodHandle newThreadPerTaskExecutorFactory = lookup.findStatic(Executors.class, "newThreadPerTaskExecutor", MethodType.methodType(ExecutorService.class, ThreadFactory.class)); + + NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR = name -> { + try { + Object virtualThreadBuilder = ofVirtualHandle.invoke(); + setNameHandle.invoke(virtualThreadBuilder, name, 1L); + ThreadFactory threadFactory = (ThreadFactory) toFactoryHandle.invoke(virtualThreadBuilder); + + return (ExecutorService) newThreadPerTaskExecutorFactory.invokeExact(threadFactory); + } catch (Throwable e) { + throw new AssertionError("Unreachable", e); + } + }; + } catch (Throwable e) { + throw new AssertionError("Unreachable", e); } + } else { + NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR = null; + } + } + + /// @return Returns null if the Java version is below 21, otherwise always returns a non-null value. + public static ExecutorService newVirtualThreadPerTaskExecutor(String name) { + if (NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR == null) { + return null; } - return IO_EXECUTOR; + return NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR.apply(name); + } + + /// This thread pool is suitable for network and local I/O operations. + /// + /// For Java 21 or later, all tasks will be dispatched to virtual threads. + /// + /// @return Thread pool for I/O operations. + public static ExecutorService io() { + return Holder.IO_EXECUTOR; } public static Executor javafx() { return Platform::runLater; } + /// Default thread pool, equivalent to [ForkJoinPool#commonPool()]. + /// + /// It is recommended to perform computation tasks on this thread pool. For I/O operations, please use [#io()]. public static Executor defaultScheduler() { return ForkJoinPool.commonPool(); } @@ -70,9 +103,18 @@ public final class Schedulers { // shutdownNow will interrupt all threads. // So when we want to close the app, no threads need to be waited for finish. // Sometimes it resolves the problem that the app does not exit. + } - if (IO_EXECUTOR != null) - IO_EXECUTOR.shutdownNow(); + private static final class Holder { + private static final ExecutorService IO_EXECUTOR; + + static { + //noinspection resource + ExecutorService vtExecutor = newVirtualThreadPerTaskExecutor("IO"); + IO_EXECUTOR = vtExecutor != null + ? vtExecutor + : Executors.newCachedThreadPool(Lang.counterThreadFactory("IO", true)); + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index 3fabc93ba..88670f265 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -236,14 +236,22 @@ public final class Lang { } public static ThreadPoolExecutor threadPool(String name, boolean daemon, int threads, long timeout, TimeUnit timeunit) { + ThreadPoolExecutor pool = new ThreadPoolExecutor( + threads, threads, + timeout, timeunit, + new LinkedBlockingQueue<>(), + counterThreadFactory(name, daemon)); + pool.allowCoreThreadTimeOut(true); + return pool; + } + + public static ThreadFactory counterThreadFactory(String name, boolean daemon) { AtomicInteger counter = new AtomicInteger(1); - ThreadPoolExecutor pool = new ThreadPoolExecutor(threads, threads, timeout, timeunit, new LinkedBlockingQueue<>(), r -> { + return r -> { Thread t = new Thread(r, name + "-" + counter.getAndIncrement()); t.setDaemon(daemon); return t; - }); - pool.allowCoreThreadTimeOut(true); - return pool; + }; } public static int parseInt(Object string, int defaultValue) {