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

Java Thread启动触发IllegalThreadStateException问题排查

问题:多角色拖动到棋盘时触发IllegalThreadStateException

我正在开发一款类似《皇室战争》的手游项目,目前用AnimationTimer在AnchorPane上实现角色移动,用Thread模拟两个角色间的战斗,负责检测双方伤害与生命值。但拖动多个角色到棋盘上时,出现了以下错误:

Exception in thread "JavaFX Application Thread" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at clash.royale/clash.royale.model.ActiveCards.fight(ActiveCards.java:157)
at clash.royale/clash.royale.model.ActiveCards$1.handle(ActiveCards.java:141)
at javafx.graphics/javafx.animation.AnimationTimer$AnimationTimerReceiver.lambda$handle$0(AnimationTimer.java:57)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/javafx.animation.AnimationTimer$AnimationTimerReceiver.handle(AnimationTimer.java:56)
at javafx.graphics/com.sun.scenario.animation.AbstractMasterTimer.timePulseImpl(AbstractMasterTimer.java:357)
at javafx.graphics/com.sun.scenario.animation.AbstractMasterTimer$MainLoop.run(AbstractMasterTimer.java:267)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:515)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:499)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:492)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:320)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:829)   
相关代码
public class ActiveCards extends Thread {
double health;
double damage;
int path;
ProgressBar healthBar;
Pane whole = new Pane();
static ArrayList<ActiveCards> active = new ArrayList<>();
boolean enemydetected = false;
boolean samewidths;
boolean sameheights;
Pane enemie;
boolean exit = false;

boolean started = false;


ArrayList<ActiveCards> onBoardCards = new ArrayList<>();

AnimationTimer move = new AnimationTimer() {
    private long lastUpdate = 0;
    private long walk = 0;

    @Override
    public void handle(long l) {

        if(l-walk >= 800_000_000.0) {
            if (whole.getRotate() == -3 || whole.getRotate() == 0 && !enemydetected) {
                whole.setRotate(5);
            }else if (whole.getRotate() == 5) {
                whole.setRotate(-3);
            }
            walk = l;
        }

        if (l - lastUpdate >= 12_000_000) {

            if (!enemydetected) {
                if (whole.getLayoutX() < 240) {
                    whole.setLayoutX(whole.getLayoutX() + 1.5);
                }

                if (whole.getLayoutY() <= 150) {
                    path = 60;
                } else if (whole.getLayoutY() > 150) {
                    path = 240;
                }

                if (whole.getLayoutY() > path - 2 && whole.getLayoutY() < path + 1) {
                    if (whole.getLayoutX() < 500 && whole.getLayoutX() >= 240) {
                        whole.setLayoutX(whole.getLayoutX() + 1.5);
                    }
                } else if (whole.getLayoutY() < path) {
                    whole.setLayoutY(whole.getLayoutY() + 1.5);
                } else if (whole.getLayoutY() > path) {
                    whole.setLayoutY(whole.getLayoutY() - 1.5);
                }


                for (ActiveCards a : active) {
                    double underX = whole.getLayoutX() - 95;
                    double overY = whole.getLayoutY() - 95;
                    double overX = whole.getLayoutX() + 95;
                    double underY = whole.getLayoutY() + 95;

                    if (a.whole != whole && !a.enemydetected && !enemydetected) {
                        if (a.whole.getLayoutY() >= overY && a.whole.getLayoutY() <= whole.getLayoutY() && a.whole.getLayoutX() >= underX && a.whole.getLayoutX() <= overX) {
                            enemydetected = true;
                            synchronized (a) {
                                a.enemydetected = true;
                            }
                            a.started = true;
                            enemie = a.whole;
                            a.enemie = whole;
                        } else if (a.whole.getLayoutY() <= underY && a.whole.getLayoutY() >= whole.getLayoutY() && a.whole.getLayoutX() >= underX && a.whole.getLayoutX() <= overX) {
                            enemydetected = true;
                            synchronized (a) {
                                a.enemydetected = true;
                            }
                            enemie = a.whole;
                            a.enemie = whole;
                            a.started = true;
                        }
                    }



                }
            }

            if (enemydetected) {

                //Reset movement Rotation
                if(whole.getRotate() == 5 || enemie.getRotate() == -5 ||whole.getRotate() == -5 || enemie.getRotate() == 5){
                    whole.setRotate(0);
                    enemie.setRotate(0);
                }

                if (whole.getLayoutY() < enemie.getLayoutY() - 1) {
                    whole.setLayoutY(whole.getLayoutY() + 1);
                } else if (whole.getLayoutY() > enemie.getLayoutY() + 1) {
                    whole.setLayoutY(whole.getLayoutY() - 1);
                } else {
                    samewidths = true;
                }


                if (whole.getLayoutX() < enemie.getLayoutX() - 32) {
                    whole.setLayoutX(whole.getLayoutX() + 1);
                } else if (whole.getLayoutX() > enemie.getLayoutX() + 32) {
                    whole.setLayoutX(whole.getLayoutX() - 1);
                } else {
                    sameheights = true;
                }

                if (sameheights && samewidths && !started) {
                        started = true;
                        onBoardCards = active;
                    try {
                        fight();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }

        lastUpdate = l;
    }
};

public void fight() throws InterruptedException {
    if(!this.isAlive()) {
        this.start();
    }
}


ActiveCards(int health, int damage, Image p, double x, double y, Pane playable) {
    ImageView iv = new ImageView();
    iv.setImage(p);
    iv.setFitHeight(30);
    iv.setFitWidth(30);
    whole.setPrefWidth(30);
    whole.setPrefHeight(30);
    whole.setLayoutX(x - 130);
    whole.setLayoutY(y - 70);

    healthBar = new ProgressBar();
    healthBar.setLayoutY(33);
    healthBar.setPrefHeight(10);
    healthBar.setPrefWidth(32);
    healthBar.setProgress(1);
    whole.getChildren().add(healthBar);
    whole.getChildren().add(iv);


    playable.getChildren().add(whole);
    playable.toFront();
    this.health = health;
    this.damage = damage;
}


public void run() {
        int i = 0;
            while (onBoardCards.size() > i && !exit) {
                ActiveCards a = onBoardCards.get(i);
                i++;
                if (a.whole == enemie) {

                    double setHealthBar = damage / a.health;
                    double setHealthBarThis = a.damage / health;
                    while (a.health > 0 && health > 0) {

                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                a.healthBar.setProgress(a.healthBar.getProgress() - setHealthBar);
                            }
                        });

                        a.health = a.health - damage;
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        if (a.health > 0) {
                            Platform.runLater(new Runnable() {
                                @Override
                                public void run() {
                                    healthBar.setProgress(healthBar.getProgress() - setHealthBarThis);
                                }
                            });
                            health = health - a.damage;
                            try {
                                TimeUnit.SECONDS.sleep(1);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }

                    if (a.health <= 0) {
                        a.whole.setVisible(false);
                        a.whole.setDisable(true);
                        a.move.stop();
                        active.remove(a);
                        enemydetected = false;
                        this.started = false;
                        exit = true;
                    }
                    if (health <= 0) {
                        whole.setVisible(false);
                        whole.setDisable(true);
                        active.remove(this);
                        move.stop();
                        a.enemydetected = false;
                        a.started = false;
                        exit = true;
                    }
                }
            }
        }
问题原因与修复方案

错误原因

IllegalThreadStateException 抛出的核心原因是:同一个Thread对象被多次调用start()方法。

在当前代码中,ActiveCards继承了Thread,当两个角色进入战斗后,fight()方法会检查线程是否存活,若未存活则启动线程。但问题在于:当一个角色已经完成战斗(线程执行完毕进入终止状态),如果再次触发战斗检测,this.isAlive()会返回false,此时调用this.start()就会抛出异常——Java规定Thread对象只能被启动一次,终止后无法再次启动。

另外,ActiveCards既作为角色数据模型又继承Thread的设计耦合性过高,容易导致线程管理混乱。

修复方案

方案1:避免重复启动同一线程

给ActiveCards新增一个标记,确保线程只被启动一次:

// 在ActiveCards类中新增变量
private boolean isThreadStarted = false;

public void fight() throws InterruptedException {
    if(!isThreadStarted) {
        this.start();
        isThreadStarted = true;
    }
}

此方法仅能临时规避异常,无法支持角色重复战斗的需求,且继承Thread的耦合问题仍存在。

方案2:每次战斗创建新线程

放弃让ActiveCards继承Thread,在需要战斗时创建新线程:

// 修改ActiveCards,不再继承Thread
public class ActiveCards {
    // ... 原有属性不变

    public void fight() {
        // 每次战斗创建新线程
        new Thread(() -> {
            int i = 0;
            while (onBoardCards.size() > i && !exit) {
                // ... 原有run方法内的逻辑完全复制过来
            }
        }).start();
    }

    // 移除原有的run方法
}

方案3:用JavaFX Timeline替代Thread处理战斗

战斗逻辑涉及UI更新(血条变化),使用JavaFX的Timeline更符合UI线程规范,无需手动调用Platform.runLater():

// 建议新增maxHealth变量存储初始生命值,避免血条计算错误
private double maxHealth;

// 修改构造方法,初始化maxHealth
ActiveCards(int health, int damage, Image p, double x, double y, Pane playable) {
    this.maxHealth = health;
    // ... 原有构造逻辑不变
}

public void fight() {
    Timeline fightTimeline = new Timeline();
    KeyFrame attackFrame = new KeyFrame(Duration.seconds(1), event -> {
        ActiveCards enemy = findEnemyByPane(enemie);
        if (enemy != null && enemy.health > 0 && this.health > 0) {
            // 对敌方造成伤害
            enemy.health -= this.damage;
            enemy.healthBar.setProgress(enemy.health / enemy.maxHealth);

            if (enemy.health <= 0) {
                enemy.whole.setVisible(false);
                active.remove(enemy);
                enemydetected = false;
                started = false;
                fightTimeline.stop();
                return;
            }

            // 敌方反击
            this.health -= enemy.damage;
            this.healthBar.setProgress(this.health / this.maxHealth);

            if (this.health <= 0) {
                this.whole.setVisible(false);
                active.remove(this);
                enemy.enemydetected = false;
                enemy.started = false;
                fightTimeline.stop();
            }
        }
    });

    fightTimeline.getKeyFrames().add(attackFrame);
    fightTimeline.setCycleCount(Animation.INDEFINITE);
    fightTimeline.play();
}

// 辅助方法:通过Pane找到对应的ActiveCards对象
private ActiveCards findEnemyByPane(Pane pane) {
    for (ActiveCards card : active) {
        if (card.whole == pane) {
            return card;
        }
    }
    return null;
}

此方案完全在JavaFX的UI线程机制内运行,无需手动处理线程同步,同时彻底避免了Thread重复启动的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:01:14