4

我有一个程序需要更新另一个线程上JList的内容。DefaultListModel由于内容的数量可能会不时发生变化,所以我只是清除所有内容并DefaultListModel在更新时添加新内容。但似乎我遇到了一个问题,JFrame在我的线程进行更新时开始刷新。我有这样的例外

Exception in thread "AWT-EventQueue-0" 
java.lang.ArrayIndexOutOfBoundsException: 3

这是代码示例

    DefaultListModel model;
    JList jList;
    JScrollPane jScrollPane;

    Thread thread;
    public Frame() {
        this.setTitle("ASM_SIMULATOR");
        this.setBounds(100, 100, 500, 500);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.getContentPane().setLayout(null);

        model = new DefaultListModel();
        jList = new JList(model);
        jScrollPane = new JScrollPane(jList);

        jList.setBounds(50, 50, 300, 200);
        jScrollPane.setBounds(50, 50, 300, 200);

        this.getContentPane().add(jScrollPane);

        this.setVisible(true);

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {

                    makeData();
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        });

        thread.start();
    }

    public void makeData() {
        System.out.println("makeData()");
        model.clear();

        for (int i = 0; i < 20; i++) {
            model.addElement((int) (Math.random() * 100));
        }

    }

    public static void main(String[] args) {
        new Frame();

    }
4

4 回答 4

3

基本答案是不要

Swing 不是线程安全的。

您需要做的是使用 aSwingWorker构建模型并使用它的done/process方法将其应用回视图或使用SwingUtilities.invokeLater继续使用您的线程,但将更新同步回事件调度线程

阅读Swing 中的并发以了解详细信息

于 2012-10-27T07:18:12.420 回答
3

您在该代码片段中两次违反了基本的“所有 Swing 组件都应在事件调度线程 (=EDT) 上访问/修改,并且仅在 EDT 上”。

  1. 您的主要方法应该将调用包装new Frame()在一个SwingUtilities#invokeLater或一些类似的方法中
  2. 您的模型更新线程在后台线程上更改模型。更新模型将触发由 接收到的事件JList,在这些事件上JList更新本身(再次,在错误的线程上)。

两种可能的解决方案:

  • 在您的后台线程上创建一个新DefaultListModel线程,并在 EDT 上一次性替换它。
  • 继续更新现有模型,但确保更新发生在 EDT。
于 2012-10-27T07:37:31.040 回答
2
  1. 您在 Swing 中遇到并发问题

  2. 必须包裹model.addElement((int) (Math.random() * 100));invokeLater

  3. 正确的方法可能是从 开始工作人员ThreadRunnable#Thread或使用SwingWorker

  4. SwingWorkers 方法的输出publish()process()可以在EDT

于 2012-10-27T07:19:09.767 回答
0

不幸的是,事情并没有那么简单。应该只允许 GUI 线程更新 GUI,因此任何其他线程都需要通过SwingUtilities.InvokeLater. 在您的情况下,您可能只包装整个makeData方法,因为它所做的只是更新 GUI:

    thread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {

                SwingUtilities.InvokeLater(new Runnable() {
                      public void run() {
                          makeData();
                      }
                });
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    });

请注意,现在的代码makeData将在 GUI 线程上执行。在其他情况下,当您执行不涉及 GUI 的其他耗时工作时,您应该InvokeLater以更细粒度的方式使用,以尽可能保持 UI 线程自由。

编辑:更仔细地查看您的代码,我注意到您所做的只是每 200 毫秒定时更新一次 GUI。您可以通过以下方式更轻松地做到这一点javax.swing.Timer

int delay = 200; //milliseconds
ActionListener taskPerformer = new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
        makeData();
    }
};
new Timer(delay, taskPerformer).start();
于 2012-10-27T08:39:04.160 回答