Java多线程同步需求:等待线程执行完成后修改类属性
解决Java线程等待任务完成后执行后续操作的问题
嘿,我明白你的问题了——你现在需要让zoom和zoom1完成所有放大、图像像素复制这些操作后,再去调用activarBoolTrue()方法,而且之前没接触过Java线程对吧?我一步步给你讲怎么实现,保证清晰易懂。
首先得说你当前代码的小问题:现在所有逻辑都跑在Swing的鼠标点击事件线程(也就是EDT,事件调度线程)里,那两个400x400的像素循环是挺耗时的,会直接卡住你的界面,而且你想等这些操作做完再执行后续方法,所以必须把这些耗时活儿放到后台线程里,然后等它们全部完成后再触发那两个方法。
具体实现步骤
我给你两种常用的方案,你选哪个都行:
方案一:用CountDownLatch等待多个任务完成
CountDownLatch是Java里专门用来等多个线程完成的工具类,初始化时设好要等的任务数量,每个任务做完就“倒计时”一次,等倒计时到0,就可以执行后续操作了。
重构后的鼠标点击事件代码如下:
public void mouseClicked(MouseEvent arg0) { // 初始化计数器,要等2个任务(zoom和zoom1) CountDownLatch taskLatch = new CountDownLatch(2); // 封装zoom的处理任务 Runnable zoomProcessingTask = () -> { try { if (auxContadorZoom < 3 && auxContadorZoom >= 0) { zoom.aumentar(100); // 耗时的像素复制操作 for (int i = 0; i < 400; i++) { for (int j = 0; j < 400; j++) { image2.setRGB(i, j, zoom.enviar().getRGB(i, j)); } } // 注意:Swing组件的更新必须回到EDT线程! SwingUtilities.invokeLater(() -> { label2.setIcon(new ImageIcon(zoom.enviar())); label2.removeAll(); label2.add(zoom); label2.repaint(); auxContadorZoom++; }); } } finally { // 不管任务成功失败,都让计数器减1 taskLatch.countDown(); } }; // 封装zoom1的处理任务 Runnable zoom1ProcessingTask = () -> { try { if (auxContadorZoom1 < 3 && auxContadorZoom1 >= 0) { zoom1.aumentar(100); for (int i = 0; i < 400; i++) { for (int j = 0; j < 400; j++) { image.setRGB(i, j, zoom1.enviar().getRGB(i, j)); } } SwingUtilities.invokeLater(() -> { label.setIcon(new ImageIcon(zoom1.enviar())); label.removeAll(); label.add(zoom1); label.repaint(); auxContadorZoom1++; }); } } finally { taskLatch.countDown(); } }; // 启动两个后台线程执行任务 new Thread(zoomProcessingTask).start(); new Thread(zoom1ProcessingTask).start(); // 启动一个专门等待的线程,等两个任务都做完再执行后续方法 new Thread(() -> { try { // 阻塞等待,直到计数器变为0(两个任务都完成) taskLatch.await(); // 现在可以安全执行你要等的方法了 zoom1.activarBoolTrue(); zoom.activarBoolTrue(); // 如果这两个方法涉及UI更新,记得也要用SwingUtilities.invokeLater包裹 // SwingUtilities.invokeLater(() -> { // zoom1.activarBoolTrue(); // zoom.activarBoolTrue(); // }); } catch (InterruptedException e) { // 处理线程中断异常,恢复中断状态是好习惯 Thread.currentThread().interrupt(); e.printStackTrace(); } }).start(); }
方案二:用ExecutorService和Future管理线程
如果你想更规范地管理线程资源,可以用线程池ExecutorService,通过Future对象来等待任务完成:
public void mouseClicked(MouseEvent arg0) { // 创建一个固定大小的线程池,容纳2个线程 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 封装zoom任务(和方案一的任务内容一样) Runnable zoomTask = () -> { try { if (auxContadorZoom < 3 && auxContadorZoom >= 0) { zoom.aumentar(100); for (int i = 0; i < 400; i++) { for (int j = 0; j < 400; j++) { image2.setRGB(i, j, zoom.enviar().getRGB(i, j)); } } SwingUtilities.invokeLater(() -> { label2.setIcon(new ImageIcon(zoom.enviar())); label2.removeAll(); label2.add(zoom); label2.repaint(); auxContadorZoom++; }); } } catch (Exception e) { e.printStackTrace(); } }; // 封装zoom1任务 Runnable zoom1Task = () -> { try { if (auxContadorZoom1 < 3 && auxContadorZoom1 >= 0) { zoom1.aumentar(100); for (int i = 0; i < 400; i++) { for (int j = 0; j < 400; j++) { image.setRGB(i, j, zoom1.enviar().getRGB(i, j)); } } SwingUtilities.invokeLater(() -> { label.setIcon(new ImageIcon(zoom1.enviar())); label.removeAll(); label.add(zoom1); label.repaint(); auxContadorZoom1++; }); } } catch (Exception e) { e.printStackTrace(); } }; // 提交任务到线程池,获取Future对象用来等待任务完成 Future<?> zoomFuture = threadPool.submit(zoomTask); Future<?> zoom1Future = threadPool.submit(zoom1Task); // 等待任务完成并执行后续方法 new Thread(() -> { try { // 等待两个任务都执行完毕 zoomFuture.get(); zoom1Future.get(); // 执行目标方法 zoom1.activarBoolTrue(); zoom.activarBoolTrue(); // 关闭线程池,释放资源 threadPool.shutdown(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); // 出现异常时强制关闭线程池 threadPool.shutdownNow(); } }).start(); }
关键注意点
- 别阻塞EDT线程:Swing的所有UI操作都在EDT里执行,耗时任务必须放到后台线程,否则界面会卡死。
- UI操作必须回EDT:所有修改Swing组件(比如label的图标、repaint)的代码,一定要用
SwingUtilities.invokeLater()包裹,因为Swing不是线程安全的。 - 异常处理:不管用哪种方案,都要处理线程中断或执行异常,避免程序崩溃。
内容的提问来源于stack exchange,提问作者DARYZ Fifa
相关产品推荐
相关产品推荐

