Java多线程异常:创建两个含while循环的线程仅一个运行,求因
嘿,我来帮你揪出这个线程只跑一个的问题!结合你说的“生成障碍物+移动障碍物”两个线程场景,再加上Java多线程的常见坑,大概率是下面这几个原因之一,我给你逐个分析并附上解决方案:
一、最容易踩的坑:线程没正确启动
很多新手会把thread.run()当成启动线程的方法,但其实run()只是在当前线程里执行代码,根本不会开启新线程!这样两个线程的逻辑会串行执行,看起来就像只有一个在工作。
正确的打开方式必须是用start():
// 生成障碍物的线程 Thread spawnThread = new Thread(new SpawnTask()); // 移动障碍物的线程 Thread moveThread = new Thread(new MoveTask()); spawnThread.start(); // 启动新线程 moveThread.start(); // 启动另一个新线程
二、while循环独占CPU,导致另一个线程“抢不到资源”
如果你的while循环是无休无止的空转(比如没有休眠),那这个线程会把CPU占得死死的,另一个线程几乎没机会执行。比如生成障碍物的线程如果写成这样:
while (true) { // 疯狂生成障碍物,一秒生成几百个都有可能 obstacles.add(new Obstacle(...)); }
这时候移动线程根本抢不到CPU时间,自然看起来没在工作。解决办法很简单:给循环加个休眠,控制生成频率的同时让出CPU:
// 用一个volatile变量控制游戏运行状态,方便优雅终止 private volatile boolean isGameRunning = true; // 生成线程的循环 while (isGameRunning) { // 生成一个障碍物 synchronized (obstacles) { // 加锁保护共享集合 obstacles.add(new Obstacle(800, ThreadLocalRandom.current().nextInt(150, 450))); } try { Thread.sleep(2000); // 每2秒生成一个,合理控制频率 } catch (InterruptedException e) { // 处理中断,优雅结束线程 Thread.currentThread().interrupt(); isGameRunning = false; break; } }
三、共享资源没同步,导致操作被“吞掉”
你用了Vector存储障碍物(它本身是线程安全的,但遍历/修改组合操作还是可能出问题),如果两个线程同时操作这个集合,比如移动线程在遍历的时候生成线程在添加,可能会导致移动线程看不到新生成的障碍物,或者出现并发修改异常,看起来就像生成线程没工作。
正确的做法是对共享集合的操作加锁:
// 移动线程的循环逻辑 while (isGameRunning) { synchronized (obstacles) { // 加锁,保证同一时间只有一个线程操作集合 for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.x -= 5; // 向左移动 // 移除超出屏幕的障碍物 if (obs.x < -obs.width) { obstacles.remove(i); } } } try { Thread.sleep(16); // 控制移动速度,大概60帧/秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); isGameRunning = false; break; } // 通知UI刷新(必须在Swing的EDT线程执行) SwingUtilities.invokeLater(() -> gamePanel.repaint()); }
四、Swing UI线程的干扰
因为你用了Swing,所有UI操作(比如画障碍物)必须在**事件调度线程(EDT)**里执行!如果你的线程直接在run方法里调用g.draw()之类的UI操作,会导致线程安全问题,甚至阻塞UI线程,让你误以为是业务线程没工作。
正确的姿势是:业务线程只修改数据(比如障碍物的位置、集合),然后通过SwingUtilities.invokeLater()让UI在EDT里刷新:
// 移动线程修改完障碍物位置后,通知UI重绘 SwingUtilities.invokeLater(() -> { gamePanel.repaint(); // 这个操作会触发paintComponent方法,在EDT里执行 });
给你补个可运行的简化示例
我写了个极简版的Flappy Bird线程逻辑,你可以对照自己的代码找差异:
import javax.swing.*; import java.awt.*; import java.util.Vector; import java.util.concurrent.ThreadLocalRandom; public class FlappyBirdDemo extends JFrame { private Vector<Obstacle> obstacles = new Vector<>(); private volatile boolean isGameRunning = true; private GamePanel gamePanel; public FlappyBirdDemo() { // 初始化窗口 setTitle("Flappy Bird Thread Demo"); setSize(800, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 初始化游戏面板 gamePanel = new GamePanel(); add(gamePanel); setVisible(true); // 启动生成障碍物线程 new Thread(this::spawnObstacles).start(); // 启动移动障碍物线程 new Thread(this::moveObstacles).start(); } private void spawnObstacles() { while (isGameRunning) { int gapY = ThreadLocalRandom.current().nextInt(150, 450); synchronized (obstacles) { obstacles.add(new Obstacle(800, gapY)); } try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); isGameRunning = false; } } } private void moveObstacles() { while (isGameRunning) { synchronized (obstacles) { for (int i = obstacles.size() - 1; i >= 0; i--) { Obstacle obs = obstacles.get(i); obs.x -= 5; if (obs.x < -obs.width) { obstacles.remove(i); } } } SwingUtilities.invokeLater(gamePanel::repaint); try { Thread.sleep(16); } catch (InterruptedException e) { Thread.currentThread().interrupt(); isGameRunning = false; } } } private class GamePanel extends JPanel { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 绘制背景 g.setColor(Color.CYAN); g.fillRect(0, 0, getWidth(), getHeight()); // 绘制障碍物 synchronized (obstacles) { for (Obstacle obs : obstacles) { g.setColor(Color.GREEN); // 上管道 g.fillRect(obs.x, 0, obs.width, obs.gapY - 100); // 下管道 g.fillRect(obs.x, obs.gapY + 100, obs.width, getHeight() - (obs.gapY + 100)); } } } } private static class Obstacle { int x; int gapY; int width = 80; public Obstacle(int x, int gapY) { this.x = x; this.gapY = gapY; } } public static void main(String[] args) { // 在EDT线程启动UI,这是Swing的规范 SwingUtilities.invokeLater(FlappyBirdDemo::new); } }
快速排查步骤
- 先检查两个线程是不是都用
start()启动了,别用run()! - 看while循环里有没有
Thread.sleep(),避免一个线程独占CPU - 检查共享资源(比如障碍物集合)的操作有没有加锁同步
- 确保所有UI操作都在EDT线程执行,别在业务线程里直接画东西
内容的提问来源于stack exchange,提问作者Frabazz

