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
相关产品推荐
相关产品推荐

