线程运行时Java多线程AWT绘图程序无法关闭及操作求助
多线程AWT绘图程序无法关闭的问题修复
核心问题分析
- GUI事件线程(EDT)被阻塞:MyCanvas的
paint()方法里包含持续的while (!Marble.isDone())循环,配合Util.delay()的忙等逻辑,直接霸占了负责处理所有GUI事件(窗口关闭、按钮点击等)的EDT,导致用户操作完全无法响应。 - 线程可见性问题:Marble类中的
done静态变量未加volatile修饰,多线程环境下其他线程对done的修改无法被主线程或其他线程及时感知。 - 不安全的绘图操作:直接在非EDT线程中使用Canvas的Graphics对象绘图,违反AWT/Swing线程安全规则,可能引发绘图异常或线程阻塞。
- 低效的忙等延迟:
Util.delay()用忙循环实现延迟,持续占用CPU资源,加剧线程阻塞问题。
修复步骤
- 释放EDT,移除paint方法中的阻塞循环,仅保留线程初始化逻辑。
- 给Marble的
done变量添加volatile修饰符,确保多线程可见性。 - 替换
Util.delay()为Thread.sleep(),避免CPU空转并正确处理中断异常。 - 使用双缓冲绘图:创建BufferedImage作为绘图缓冲区,所有Marble线程先绘制到缓冲区,再由EDT统一绘制到Canvas,保证线程安全。
- 优化线程初始化逻辑,避免paint方法重复调用时创建多组线程。
修改后的完整代码
// MultiThreadedSnakes.java import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*; public class MultiThreadedSnakes { public static void main(String args[]) { SwingUtilities.invokeLater(() -> { Snakes tester = new Snakes(); tester.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }); } } class Snakes extends JFrame { private Container contentPane; private MyCanvas canvas; private JButton stopButton; public Snakes() { super("MultiThreadedSnakes"); stopButton = new JButton("Stop Execution and Close Window"); stopButton.setFont(new Font("Arial", Font.BOLD, 24)); stopButton.addActionListener(evt -> { Marble.terminate(); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.exit(0); }); contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(stopButton, BorderLayout.PAGE_END); canvas = new MyCanvas(); contentPane.add(canvas, BorderLayout.CENTER); setSize(1320, 820); setVisible(true); } } class Marble implements Runnable { private Marble[] marbles; private int width, height; private int size; private int xCoord, yCoord; private int incX, incY; private Color clr; private BufferedImage buffer; private static volatile boolean done = false; // 添加volatile保证多线程可见性 public Marble(int w, int h, int x, int y, int adjustX, int adjustY, BufferedImage buf, Color c, Marble[] m) { marbles = m; width = w; height = h; size = 30; xCoord = x; yCoord = y; incX = adjustX; incY = adjustY; clr = c; this.buffer = buf; } public static void terminate() { done = true; } public static boolean isDone() { return done; } public void moveMarble() { Random rnd = new Random(); xCoord += incX; yCoord += incY; if (xCoord <= 0) incX = (rnd.nextInt(11) + 1); else if (xCoord >= width - size) incX = -(rnd.nextInt(11) + 1); if (yCoord <= 0) incY = (rnd.nextInt(11) + 1); else if (yCoord >= height - size) incY = -(rnd.nextInt(11) + 1); } public void showMarble() { synchronized (marbles) { Graphics g = buffer.getGraphics(); g.setColor(clr); g.fillOval(xCoord, yCoord, size, size); g.dispose(); } } public void run() { while (!done) { showMarble(); try { Thread.sleep(10); // 替换忙等为标准线程休眠 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } moveMarble(); } } } class MyCanvas extends Canvas { private int maxX, maxY; private int numMarbles; private Marble[] marbles; private Thread[] threads; private BufferedImage buffer; public MyCanvas() { setSize(1320, 760); maxX = 1300; maxY = 700; numMarbles = 8; marbles = new Marble[numMarbles]; threads = new Thread[numMarbles]; // 初始化双缓冲图像 buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); } public void paint(Graphics g) { // 仅初始化一次线程,避免重复创建 if (threads[0] == null) { Graphics bufG = buffer.getGraphics(); bufG.setColor(Color.white); bufG.fillRect(0, 0, getWidth(), getHeight()); bufG.dispose(); marbles[0] = new Marble(maxX, maxY, 50, 50, 5, 5, buffer, Color.red, marbles); marbles[1] = new Marble(maxX, maxY, 100, 100, 5, 5, buffer, Color.green, marbles); marbles[2] = new Marble(maxX, maxY, 150, 150, 5, 5, buffer, Color.blue, marbles); marbles[3] = new Marble(maxX, maxY, 200, 200, 5, 5, buffer, Color.orange, marbles); marbles[4] = new Marble(maxX, maxY, 250, 250, 5, 5, buffer, Color.gray, marbles); marbles[5] = new Marble(maxX, maxY, 300, 300, 5, 5, buffer, Color.pink, marbles); marbles[6] = new Marble(maxX, maxY, 350, 350, 5, 5, buffer, Color.yellow, marbles); marbles[7] = new Marble(maxX, maxY, 400, 400, 5, 5, buffer, Color.black, marbles); for (int t = 0; t < numMarbles; t++) { threads[t] = new Thread(marbles[t]); threads[t].start(); } } // 将缓冲区绘制到Canvas g.drawImage(buffer, 0, 0, this); // 主动触发重绘更新画面 if (!Marble.isDone()) { repaint(); } } @Override public void update(Graphics g) { // 覆盖update方法,避免默认清屏操作,减少画面闪烁 paint(g); } } class Util { public static int random(int min, int max) { int range = max - min + 1; return (int) (Math.random() * range + min); } }
关键修改说明
- 释放EDT:移除paint方法中的阻塞循环,改用
repaint()主动更新画面,让EDT能正常处理GUI事件。 - volatile修饰done:确保所有线程能及时感知done变量的变化,避免线程无限循环。
- Thread.sleep替代忙等:降低CPU占用,同时正确处理中断,让线程能响应终止信号。
- 双缓冲绘图:统一绘图流程,符合AWT线程安全规则,减少画面闪烁。
- 线程初始化判断:避免paint方法重复调用时创建多组线程,防止资源浪费。
内容的提问来源于stack exchange,提问作者John Schram
相关产品推荐
相关产品推荐

