You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程异常:创建两个含while循环的线程仅一个运行,求因

你的Flappy Bird线程问题排查指南

嘿,我来帮你揪出这个线程只跑一个的问题!结合你说的“生成障碍物+移动障碍物”两个线程场景,再加上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);
    }
}

快速排查步骤

  1. 先检查两个线程是不是都用start()启动了,别用run()!
  2. 看while循环里有没有Thread.sleep(),避免一个线程独占CPU
  3. 检查共享资源(比如障碍物集合)的操作有没有加锁同步
  4. 确保所有UI操作都在EDT线程执行,别在业务线程里直接画东西

内容的提问来源于stack exchange,提问作者Frabazz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:32:50