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 02a566e2d..394224d52 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -58,6 +58,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import java.lang.ref.WeakReference; @@ -706,6 +707,7 @@ public final class LauncherHelper { */ private final class HMCLProcessListener implements ProcessListener { + private final ReentrantLock lock = new ReentrantLock(); private final HMCLGameRepository repository; private final Version version; private final LaunchOptions launchOptions; @@ -849,21 +851,27 @@ public final class LauncherHelper { level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO); logBuffer.add(new Log(log, level)); } else { - synchronized (this) { + lock.lock(); + try { logs.addLast(new Log(log, level)); if (logs.size() > Log.getLogLines()) logs.removeFirst(); + } finally { + lock.unlock(); } } if (!lwjgl) { String lowerCaseLog = log.toLowerCase(Locale.ROOT); if (!detectWindow || lowerCaseLog.contains("lwjgl version") || lowerCaseLog.contains("lwjgl openal")) { - synchronized (this) { + lock.lock(); + try { if (!lwjgl) { lwjgl = true; finishLaunch(); } + } finally { + lock.unlock(); } } } @@ -888,9 +896,12 @@ public final class LauncherHelper { // Game crashed before opening the game window. if (!lwjgl) { - synchronized (this) { + lock.lock(); + try { if (!lwjgl) finishLaunch(); + } finally { + lock.unlock(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PartialInflaterInputStream.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PartialInflaterInputStream.java index d43e46d1a..d4a3374b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PartialInflaterInputStream.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/util/PartialInflaterInputStream.java @@ -287,7 +287,7 @@ public class PartialInflaterInputStream extends FilterInputStream { * the mark position becomes invalid. * @see InputStream#reset() */ - public synchronized void mark(int readlimit) { + public void mark(int readlimit) { } /** @@ -302,7 +302,7 @@ public class PartialInflaterInputStream extends FilterInputStream { * @see InputStream#mark(int) * @see IOException */ - public synchronized void reset() throws IOException { + public void reset() throws IOException { throw new IOException("mark/reset not supported"); } } 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 c9828c9e4..664e29563 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java @@ -97,7 +97,7 @@ public final class Schedulers { return ForkJoinPool.commonPool(); } - public static synchronized void shutdown() { + public static void shutdown() { LOG.info("Shutting down executor services."); // shutdownNow will interrupt all threads. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/BindingMapping.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/BindingMapping.java index 2d9b13bc8..2b5f928b2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/BindingMapping.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/BindingMapping.java @@ -25,6 +25,7 @@ import javafx.beans.value.ObservableValue; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.function.Supplier; @@ -144,6 +145,7 @@ public abstract class BindingMapping extends ObjectBinding { private static final class AsyncMappedBinding extends BindingMapping { + private final ReentrantLock lock = new ReentrantLock(); private boolean initialized = false; private T prev; private U value; @@ -159,13 +161,16 @@ public abstract class BindingMapping extends ObjectBinding { } private void tryUpdateValue(T currentPrev) { - synchronized (this) { + lock.lock(); + try { if ((initialized && Objects.equals(prev, currentPrev)) || isComputing(currentPrev)) { return; } computing = true; computingPrev = currentPrev; + } finally { + lock.unlock(); } CompletableFuture task; @@ -189,7 +194,8 @@ public abstract class BindingMapping extends ObjectBinding { } private void valueUpdate(T currentPrev, U computed) { - synchronized (this) { + lock.lock(); + try { if (isComputing(currentPrev)) { computing = false; computingPrev = null; @@ -197,15 +203,20 @@ public abstract class BindingMapping extends ObjectBinding { value = computed; initialized = true; } + } finally { + lock.unlock(); } } private void valueUpdateFailed(T currentPrev) { - synchronized (this) { + lock.lock(); + try { if (isComputing(currentPrev)) { computing = false; computingPrev = null; } + } finally { + lock.unlock(); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableCache.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableCache.java index b4193f7ff..e7487423b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableCache.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ObservableCache.java @@ -24,6 +24,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import org.jackhuang.hmcl.util.function.ExceptionalFunction; @@ -37,6 +38,7 @@ import javafx.beans.binding.ObjectBinding; */ public final class ObservableCache { + private final ReentrantLock lock = new ReentrantLock(); private final ExceptionalFunction source; private final BiConsumer exceptionHandler; private final V fallbackValue; @@ -54,22 +56,29 @@ public final class ObservableCache { } public Optional getImmediately(K key) { - synchronized (this) { + lock.lock(); + try { return Optional.ofNullable(cache.get(key)); + } finally { + lock.unlock(); } } public void put(K key, V value) { - synchronized (this) { + lock.lock(); + try { cache.put(key, value); invalidated.remove(key); + } finally { + lock.unlock(); } Platform.runLater(observable::invalidate); } private CompletableFuture query(K key, Executor executor) { CompletableFuture future; - synchronized (this) { + lock.lock(); + try { CompletableFuture prev = pendings.get(key); if (prev != null) { return prev; @@ -77,6 +86,8 @@ public final class ObservableCache { future = new CompletableFuture<>(); pendings.put(key, future); } + } finally { + lock.unlock(); } executor.execute(() -> { @@ -84,18 +95,24 @@ public final class ObservableCache { try { result = source.apply(key); } catch (Throwable ex) { - synchronized (this) { + lock.lock(); + try { pendings.remove(key); + } finally { + lock.unlock(); } exceptionHandler.accept(key, ex); future.completeExceptionally(ex); return; } - synchronized (this) { + lock.lock(); + try { cache.put(key, result); invalidated.remove(key); pendings.remove(key, future); + } finally { + lock.unlock(); } future.complete(result); Platform.runLater(observable::invalidate); @@ -106,11 +123,14 @@ public final class ObservableCache { public V get(K key) { V cached; - synchronized (this) { + lock.lock(); + try { cached = cache.get(key); if (cached != null && !invalidated.containsKey(key)) { return cached; } + } finally { + lock.unlock(); } try { @@ -143,7 +163,9 @@ public final class ObservableCache { return Bindings.createObjectBinding(() -> { V result; boolean refresh; - synchronized (this) { + + lock.lock(); + try { result = cache.get(key); if (result == null) { result = fallbackValue; @@ -151,6 +173,8 @@ public final class ObservableCache { } else { refresh = invalidated.containsKey(key); } + } finally { + lock.unlock(); } if (!quiet && refresh) { query(key, executor); @@ -160,10 +184,13 @@ public final class ObservableCache { } public void invalidate(K key) { - synchronized (this) { + lock.lock(); + try { if (cache.containsKey(key)) { invalidated.put(key, Boolean.TRUE); } + } finally { + lock.unlock(); } Platform.runLater(observable::invalidate); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java index 4a746ddf6..a179a74fe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java @@ -22,17 +22,17 @@ import org.jackhuang.hmcl.util.Lang; import java.io.IOException; import java.util.*; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Predicate; -/** - * The managed process. - * - * @author huangyuhui - * @see org.jackhuang.hmcl.launch.ExitWaiter - * @see org.jackhuang.hmcl.launch.StreamPump - */ +/// The managed process. +/// +/// @author huangyuhui +/// +/// @see org.jackhuang.hmcl.launch.StreamPump public final class ManagedProcess { + private final ReentrantLock lock = new ReentrantLock(); private final Process process; private final List commands; private final String classpath; @@ -54,7 +54,7 @@ public final class ManagedProcess { */ public ManagedProcess(Process process, List commands) { this.process = process; - this.commands = Collections.unmodifiableList(new ArrayList<>(commands)); + this.commands = List.copyOf(commands); this.classpath = null; } @@ -67,7 +67,7 @@ public final class ManagedProcess { */ public ManagedProcess(Process process, List commands, String classpath) { this.process = process; - this.commands = Collections.unmodifiableList(new ArrayList<>(commands)); + this.commands = List.copyOf(commands); this.classpath = classpath; } @@ -111,9 +111,11 @@ public final class ManagedProcess { * * @see #addLine */ - public synchronized List getLines(Predicate lineFilter) { + public List getLines(Predicate lineFilter) { + lock.lock(); + if (lineFilter == null) - return Collections.unmodifiableList(Arrays.asList(lines.toArray(new String[0]))); + return List.copyOf(lines); ArrayList res = new ArrayList<>(); for (String line : this.lines) { @@ -123,8 +125,13 @@ public final class ManagedProcess { return Collections.unmodifiableList(res); } - public synchronized void addLine(String line) { - lines.add(line); + public void addLine(String line) { + lock.lock(); + try { + lines.add(line); + } finally { + lock.unlock(); + } } /** @@ -133,15 +140,20 @@ public final class ManagedProcess { * If a thread is monitoring this raw process, * you are required to add the instance by this method. */ - public synchronized void addRelatedThread(Thread thread) { - relatedThreads.add(thread); + public void addRelatedThread(Thread thread) { + lock.lock(); + try { + relatedThreads.add(thread); + } finally { + lock.unlock(); + } } - public synchronized void pumpInputStream(Consumer onLogLine) { + public void pumpInputStream(Consumer onLogLine) { addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), onLogLine, OperatingSystem.NATIVE_CHARSET), "ProcessInputStreamPump", true)); } - public synchronized void pumpErrorStream(Consumer onLogLine) { + public void pumpErrorStream(Consumer onLogLine) { addRelatedThread(Lang.thread(new StreamPump(process.getErrorStream(), onLogLine, OperatingSystem.NATIVE_CHARSET), "ProcessErrorStreamPump", true)); } @@ -172,8 +184,13 @@ public final class ManagedProcess { destroyRelatedThreads(); } - public synchronized void destroyRelatedThreads() { - relatedThreads.forEach(Thread::interrupt); + public void destroyRelatedThreads() { + lock.lock(); + try { + relatedThreads.forEach(Thread::interrupt); + } finally { + lock.unlock(); + } } @Override