Processing中mouseClicked()函数引发的线程异常问题求助
问题分析与修复方案
核心问题拆解
你的代码存在几个关键逻辑冲突,导致重复点击时线程异常:
- 同步方法阻塞引发逻辑混乱:
update、setScreenIndex、waitForThread全是synchronized方法,线程加载时重复点击触发的setScreenIndex会阻塞等待锁,同时update方法也会被卡住,无法正常轮询线程状态,最终导致线程看似存活但实际无响应。 - 线程运行时屏幕引用被篡改:重复点击会调用
setScreenIndex修改screenIndex和currentScreen,正在运行的SetupScreenThread持有的屏幕引用可能被替换,导致初始化逻辑执行到一半失效。 - 线程状态轮询不可靠:仅靠
isAlive()判断线程是否完成并不准确——线程可能因异常悄悄终止,但isAlive()返回false的时机无法保证;或是线程陷入死循环,导致isAlive()一直为true。
具体修复步骤
1. 新增加载状态拦截,直接拒绝重复请求
在Main类中添加volatile加载标记,拦截重复点击触发的无效请求:
final class Main { private volatile boolean isLoading = false; // 新增加载状态标记 public void update() // 移除全局同步,仅同步关键代码块 { try { Thread threadToCheck = null; int targetIndex = -1; // 仅同步变量读取操作 synchronized(this) { threadToCheck = threadToWaitFor; targetIndex = nextScreenIndex; } if (threadToCheck != null) { if (!threadToCheck.isAlive()) { synchronized(this) { threadToWaitFor = null; isLoading = false; // 重置加载状态 } setScreenIndex(targetIndex); } } // 同步屏幕更新操作 synchronized(this) { currentScreen.update(); } } catch (Exception e) { logger.println("ERROR: Main Update: " + e.getMessage()); logger.println("Stack Trace: "); e.printStackTrace(logger); logger.flush(); exit(); } } public synchronized void setScreenIndex(int screenIndex) { if (isLoading) { // 拦截正在加载时的重复请求 return; } this.screenIndex = screenIndex; currentScreen = screens.get(this.screenIndex); if (!currentScreen.getSetup()) { isLoading = true; // 标记进入加载状态 // 传入Main实例用于线程完成后的回调 SetupScreenThread thread = new SetupScreenThread(currentScreen, this); thread.start(); waitForThread(thread, screenIndex); } } public synchronized void waitForThread(Thread thread, int newScreenIndex) { this.threadToWaitFor = thread; this.nextScreenIndex = newScreenIndex; this.currentScreen = loadScreen; } // 新增回调方法,线程完成后调用以重置状态 public synchronized void onScreenSetupComplete() { isLoading = false; } }
2. 修改线程类,确保初始化逻辑独立且异常可控
让线程持有独立的屏幕引用,避免外部篡改,同时捕获所有异常并通知主类:
class SetupScreenThread extends Thread { private final Screen targetScreen; private final Main mainInstance; public SetupScreenThread(Screen targetScreen, Main mainInstance) { this.targetScreen = targetScreen; this.mainInstance = mainInstance; } @Override public void run() { try { targetScreen.setup(); // 执行屏幕初始化 targetScreen.setSetup(true); // 标记初始化完成 } catch (Exception e) { // 记录初始化异常,避免线程悄悄终止 mainInstance.logger.println("ERROR: Screen Setup: " + e.getMessage()); e.printStackTrace(mainInstance.logger); mainInstance.logger.flush(); } finally { mainInstance.onScreenSetupComplete(); // 无论成功失败都通知主类更新状态 } } }
3. 可选优化:替换轮询为主动通知机制
如果轮询isAlive()仍有问题,可以用CountDownLatch替代轮询,让线程完成后主动唤醒主逻辑:
// 在Main类中替换threadToWaitFor为CountDownLatch private volatile CountDownLatch setupLatch = null; // 修改setScreenIndex中的线程启动逻辑 SetupScreenThread thread = new SetupScreenThread(targetScreen, this); setupLatch = new CountDownLatch(1); thread.start(); waitForThread(screenIndex); // 修改update中的检查逻辑 if (setupLatch != null) { try { // 非阻塞等待,避免卡住UI更新 setupLatch.await(100, TimeUnit.MILLISECONDS); if (setupLatch.getCount() == 0) { setupLatch = null; isLoading = false; setScreenIndex(targetIndex); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 在SetupScreenThread的finally中调用 setupLatch.countDown();
关键注意点
- 缩小同步范围:避免全局同步方法,只同步必要的变量读写和状态更新,保证UI更新流畅性。
- 线程引用独立:线程操作的屏幕对象不要被外部修改,确保初始化逻辑完整执行。
- 强制捕获异常:线程中的未捕获异常会导致线程静默终止,必须捕获并记录,避免状态不一致。
内容的提问来源于stack exchange,提问作者EdwardT2021
相关产品推荐
相关产品推荐

