【问题标题】:Swing–JavaFX Interoperability ConcurrentModificationException bugSwing–JavaFX 互操作性 ConcurrentModificationException 错误
【发布时间】:2021-10-16 09:07:41
【问题描述】:

过去 3 天(20 多个小时)我一直在尝试解决这个 ConcurrentModificationException 错误。我正在尝试将我相当大的 Java 项目迁移到 JavaFX,但我在管理 Swing–JavaFX 互操作性和线程方面遇到了麻烦,因为我希望我的程序在这方面是混合的。

虫子

当我足够快地单击Add Particle 按钮时会出现该错误(但也来自我主项目中的多个其他来源,其他按钮也会产生相同的错误)。我得到以下堆栈跟踪

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
        at MainPanel$2.actionPerformed(MainPanel.java:40)
        at java.desktop/javax.swing.Timer.fireActionPerformed(Timer.java:317)
        at java.desktop/javax.swing.Timer$DoPostEvent.run(Timer.java:249)
        at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
        at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
        at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
        at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

下面是我可以提供的项目中最小可重复性示例 (MRE),并带有 Github link here

testProjectFx.fxml

(FXML 文件)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="SampleController">
   <children>
      <BorderPane fx:id="mainPanel" prefHeight="318.0" prefWidth="535.0" />
      <Button fx:id="addParticle" layoutX="26.0" layoutY="347.0" mnemonicParsing="false" text="Add Particle" />
   </children>
</AnchorPane>

SampleController.java

(FXML 控制器文件)

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class SampleController {

    @FXML
    private BorderPane mainPanel;

    @FXML
    private Button addParticle;

    public void initializeButtons(MainPanel mainPanel) {
        addParticle.addEventHandler(ActionEvent.ACTION, event -> mainPanel.addParticle(
            100, 5
        ));


    }

}

App.java

扩展应用程序的主类)。

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.fxml.FXMLLoader;
import java.awt.Color;

import javax.swing.SwingUtilities;

import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class App extends Application{
    public static void main(String[] args) {
        launch(args);
    }

    public static SampleController controller;

    @Override
    public void start(Stage stage) throws Exception {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("testProjectFx.fxml"));
            Parent root = loader.load();

            controller = (SampleController) loader.getController();

            Scene scene = new Scene(root, 600 , 400);

            BorderPane scenePanel = (BorderPane) scene.lookup("#mainPanel");
            SwingNode swingNode = new SwingNode();
            MainPanel mainPanel = new MainPanel();

            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    mainPanel.setSize(535, 318);
                    mainPanel.setBackground(Color.black);
                    mainPanel.physicsTimer.start();
                    mainPanel.fpsTimer.start();
                    swingNode.setContent(mainPanel);
                }
            });
            scenePanel.setCenter(swingNode);


            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    mainPanel.initializeParticles(15, 100 ,5);
                    controller.initializeButtons(mainPanel);
                }
            });
           
            
            stage.setTitle("FXML Welcome");
            stage.setScene(scene);
            stage.show();

        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

MainPanel.java

(绘画发生的JPanel)

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Arc2D;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JPanel;
import javax.swing.Timer;

public class MainPanel extends JPanel{

    static Random rand = new Random();
    static ArrayList<Particle> particleList = new ArrayList<Particle>();
    static int timeTick = 1000/60; //fps normal

    /* 
     * Timer that updates the screen every at 60 FPS. 
     */
    Timer fpsTimer = new Timer(timeTick, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
                for(Particle p : particleList) {
                    p.x += p.vx;
                    p.y += p.vy;
                }
            repaint();
        }
    });

    /* 
     * Timer that updates the physics every 1 millisecond.
     */
    Timer physicsTimer = new Timer(1 , new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
                for(Particle p1 : particleList) {       
                    for(Particle p2 : particleList) {
                        if(p1 == p2) continue;

                        // p1.edgeCollision(p2);
                        // applyForces(p1, p2);
                        // applyCollision(p1, p2);
                    }
                    p1.x += p1.vx;
                    p1.y += p1.vy;
                }   
        }
    });

    /* 
     * Function that initializes some particles on the canvas at the beggining.
     */
    public void initializeParticles(int numberOfParticles, int mass, int charge) {

        for(int i = 0 ; i < numberOfParticles ; i++) {
            int xPos;
            int yPos;
            do {
                xPos = rand.nextInt(100)+25;
                yPos = rand.nextInt(100)+25;
                Particle p = new Particle(xPos, yPos, 0, 0,mass, charge); //mass charge at end
                particleList.add(p);
            }while(!particleAlreadyExists(xPos, yPos));
            
        }
    }

    /* 
     * Draw the particles on the canvas.
     */
    public void paintComponent(Graphics g) {
        
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        for(Particle particle : particleList) {
            g2d.setColor(Color.WHITE);
            Shape circle = new Arc2D.Double(particle.x, particle.y, particle.width, particle.height,
                    0 , 360, Arc2D.CHORD);
            g2d.fill(circle);
        }
    }

    /* 
     * Checks if a particle already exists at the generated position.
     */
    public boolean particleAlreadyExists(double x, double y) {
        
        boolean particleExistsAlready = false;
        for(Particle particle : particleList) {
            if(x >= particle.x-particle.width && x <= particle.x+ 2*particle.width &&
                    y >= particle.y-particle.height && y <= particle.y + 2*particle.height) {
                particleExistsAlready=true;
                break;
            }
        }
        return particleExistsAlready;
        
    }

    public void addParticle(int mass, int charge) {
        initializeParticles(1, mass, charge);
    }

}

粒子.java

(粒子对象类)

import java.awt.Color;
import java.util.ArrayList;
import java.util.Random;


public class Particle{
    double x, y;
    double width, height;
    double mass;
    double vx, vy;
    int charge;
    public double radius = 0;
    public double centerX = 0;
    public double centerY = 0;
    static Random rand = new Random();

    public Particle(double x, double y, double velX, double velY,double mass, int charge) {
        this.x = x;
        this.y=y;
        this.width=mass/10;
        this.height=mass/10;
        this.vx=velX;
        this.vy = velY;
        this.mass=mass;
        this.charge = charge;
        this.radius = this.width/2;
        this.centerX = this.x + (this.width/2);
        this.centerY = this.y + (this.height/2);
    }


    public static double velInit() {
        double val;
        if(rand.nextBoolean()) {
            val = rand.nextDouble()*0.5;
        } else {
            val = -rand.nextDouble()*0.5;
        }
        return val;
    }

    public void reinitializeVel(ArrayList<Particle> particles) {
        for(Particle p : particles) {
            p.vx = velInit();
            p.vy = velInit();
            
        }
        
    }
}

我尝试过的事情

我尝试用

包装我的部分代码
Platform.runLater(new Runnable() { });

SwingUtilities.invokeLater(new Runnable() { });

Oracle's documentation 中所述,但没有成功。有这么多可能的组合,每一个似乎都让我的代码表现不同(和错误)。

我已经尝试了很多组合,在这一点上,我相信用这两个函数进行包装甚至可能不是解决方案。

有谁知道我做错了什么?

【问题讨论】:

  • 引用了一个纯JavaFX粒子模拟here

标签: java swing javafx


【解决方案1】:

虽然它们可能是代码的其他问题,但ConcurrentModificationException根本原因 是尝试在更改同一集合时迭代集合(在本例中为 ArrayList)。 如here 所述,这是不允许的:

 it is not generally permissible for one thread to modify a Collection while 
 another thread is iterating over it. 

initializeParticles 发布的代码中,通过向其添加元素来修改particleListparticleList.add(p);paintComponent 以及physicsTimerfpsTimer 都使用for(Particle particle : particleList) 迭代同一个集合

虽然有 several ways to overcome this issue,但最简单的方法是将 particleList 上的 all 迭代更改为以下形式:

for(int i=0; i< particleList.size(); i++) {
   Particle p = particleList.get(i);
   .........
}

【讨论】:

    【解决方案2】:

    您正在从 swing 调用 JavaFX 函数,反之亦然。如果您需要 javafx/swing 通信,使用 Platfrom.runlater 和 SwingUtilities.invokeLater 包装代码是正确的方式,但您需要以正确的方式进行;)。

    让我们从 App.java 开始 SwingNode和控制器不是JavaFX组件,而是用SwingUtilities调用的,不对。

    应该是这样的:

            SwingUtilities.invokeLater(() -> {
                mainPanel.setSize(535, 318);
                mainPanel.setBackground(Color.black);
                mainPanel.physicsTimer.start();
                mainPanel.fpsTimer.start();
                mainPanel.initializeParticles(15, 100 ,5);
            });
            
            scenePanel.setCenter(swingNode);
            swingNode.setContent(mainPanel);
            controller.initializeButtons(mainPanel);
    

    您的主要错误来自在 javafx 线程中操作particleList,而您的摆动部分正在迭代绘制函数中的列表。

    将您的 initializeParticle 函数包装在 SwingUtilities.invokeLater() 函数中应该可以解决这个问题。

    public void initializeParticles(int numberOfParticles, int mass, int charge) {
        SwingUtilities.invokeLater(() -> {
            for (int i = 0; i < numberOfParticles; i++) {
                int xPos;
                int yPos;
                do {
                    xPos = rand.nextInt(100) + 25;
                    yPos = rand.nextInt(100) + 25;
                    Particle p = new Particle(xPos, yPos, 0, 0, mass, charge); //mass charge at end
                    particleList.add(p);
                } while (!particleAlreadyExists(xPos, yPos));
            }
        });
    }
    

    但是,这是非常难看的代码,您不应该这样混合这两个框架。

    【讨论】:

      猜你喜欢
      • 2010-09-21
      • 1970-01-01
      • 2014-10-01
      • 1970-01-01
      • 2016-08-18
      • 2015-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多