From 4715a95a54c0e2b40fda4195d0181eb33291641b Mon Sep 17 00:00:00 2001 From: yuhuihuang Date: Sat, 22 Aug 2020 21:33:10 +0800 Subject: [PATCH] feat: initial multithreaded downloading --- .../jackhuang/hmcl/game/LauncherHelper.java | 4 +- .../org/jackhuang/hmcl/task/FetchTask.java | 81 +++++++++++++++++++ .../org/jackhuang/hmcl/task/Schedulers.java | 48 +++++------ 3 files changed, 104 insertions(+), 29 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index c768dbfe5..1d54cdc15 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -109,7 +109,7 @@ public final class LauncherHelper { try { checkGameState(profile, setting, version, () -> { Controllers.dialog(launchingStepsPane); - Schedulers.newThread().execute(this::launch0); + Schedulers.defaultScheduler().execute(this::launch0); }); } catch (InterruptedException | RejectedExecutionException ignore) { } @@ -199,7 +199,7 @@ public final class LauncherHelper { Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath())); }); } - }).thenRunAsync(Schedulers.defaultScheduler(), () -> { + }).thenRunAsync(() -> { launchingLatch.await(); }).withStage("launch.state.waiting_launching")) .withStagesHint(Lang.immutableListOf( diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java index d825a99d3..ecc1041f6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java @@ -34,6 +34,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -225,4 +226,84 @@ public abstract class FetchTask extends Task { NOT_CHECK_E_TAG, CACHED } + + protected class DownloadState { + private final int startPosition; + private final int endPosition; + private final int currentPosition; + private final boolean finished; + + public DownloadState(int startPosition, int endPosition, int currentPosition) { + if (currentPosition < startPosition || currentPosition > endPosition) { + throw new IllegalArgumentException("Illegal download state: start " + startPosition + ", end " + endPosition + ", cur " + currentPosition); + } + this.startPosition = startPosition; + this.endPosition = endPosition; + this.currentPosition = currentPosition; + finished = currentPosition == endPosition; + } + + public int getStartPosition() { + return startPosition; + } + + public int getEndPosition() { + return endPosition; + } + + public int getCurrentPosition() { + return currentPosition; + } + + public boolean isFinished() { + return finished; + } + } + + protected class DownloadMission { + + + + } + + private static int downloadExecutorConcurrency = Math.min(Runtime.getRuntime().availableProcessors() * 4, 64); + private static volatile ExecutorService DOWNLOAD_EXECUTOR; + + /** + * Get singleton instance of the thread pool for file downloading. + * + * @return Thread pool for FetchTask + */ + protected static ExecutorService download() { + if (DOWNLOAD_EXECUTOR == null) { + synchronized (Schedulers.class) { + if (DOWNLOAD_EXECUTOR == null) { + DOWNLOAD_EXECUTOR = new ThreadPoolExecutor(0, downloadExecutorConcurrency, 10, TimeUnit.SECONDS, new SynchronousQueue<>(), + runnable -> { + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setDaemon(true); + return thread; + }); + } + } + } + + return DOWNLOAD_EXECUTOR; + } + + public static void setDownloadExecutorConcurrency(int concurrency) { + synchronized (Schedulers.class) { + downloadExecutorConcurrency = concurrency; + if (DOWNLOAD_EXECUTOR != null) { + DOWNLOAD_EXECUTOR.shutdownNow(); + DOWNLOAD_EXECUTOR = null; + } + } + } + + public static int getDownloadExecutorConcurrency() { + synchronized (Schedulers.class) { + return downloadExecutorConcurrency; + } + } } 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 d8397207b..b53cab186 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java @@ -32,27 +32,29 @@ public final class Schedulers { private Schedulers() { } - private static volatile ThreadPoolExecutor CACHED_EXECUTOR; - - public static synchronized ThreadPoolExecutor newThread() { - if (CACHED_EXECUTOR == null) - CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory()); - - return CACHED_EXECUTOR; - } - private static volatile ExecutorService IO_EXECUTOR; - public static synchronized ExecutorService io() { + /** + * 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) { - int threads = Math.min(Runtime.getRuntime().availableProcessors() * 4, 64); - IO_EXECUTOR = Executors.newFixedThreadPool(threads, - runnable -> { - Thread thread = Executors.defaultThreadFactory().newThread(runnable); - thread.setDaemon(true); - return thread; - }); + synchronized (Schedulers.class) { + if (IO_EXECUTOR == null) { + IO_EXECUTOR = new ThreadPoolExecutor(0, 4, 10, TimeUnit.SECONDS, new SynchronousQueue<>(), + runnable -> { + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setDaemon(true); + return thread; + }); + } + } } return IO_EXECUTOR; @@ -67,7 +69,7 @@ public final class Schedulers { } public static Executor defaultScheduler() { - return newThread(); + return ForkJoinPool.commonPool(); } public static synchronized void shutdown() { @@ -77,16 +79,8 @@ public final class Schedulers { // 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 (CACHED_EXECUTOR != null) - CACHED_EXECUTOR.shutdownNow(); - if (IO_EXECUTOR != null) IO_EXECUTOR.shutdownNow(); } - public static Future schedule(Executor executor, Runnable command) { - FutureTask future = new FutureTask(command, null); - executor.execute(future); - return future; - } }