在另一个线程上更新DefaultListModel

时间:2012-10-27 06:44:34

标签: java multithreading swing jframe

我有一个程序需要在另一个帖子上更新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 个答案:

答案 0 :(得分:3)

基本答案是

Swing不是线程安全的。

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

详细阅读Concurrency in Swing

答案 1 :(得分:3)

你违反了基本的“所有Swing组件应该在Event Dispatch Thread(= EDT)上访问/修改,而​​在EDT上只有”两次在该代码片段中。

  1. 您的主要方法应将new Frame()调用包裹在SwingUtilities#invokeLater或类似方法中
  2. 您的模型更新线程会在后台线程上更改模型。更新模型将触发由JList收到的JList更新自身的事件(再次,在错误的线程上)。
  3. 两种可能的解决方案:

    • 在您的后台线程中创建一个新的DefaultListModel,并在EDT上一次性替换它。
    • 不断更新现有模型,但要确保在EDT上进行更新。

答案 2 :(得分:2)

  1. 您遇到了Concurrency in Swing

  2. 的问题
  3. 必须将model.addElement((int) (Math.random() * 100));换入invokeLater

  4. 正确的方法可能是Thread的{​​{1}}启动工作人员,或使用Runnable#Thread

  5. 来自SwingWorker的方法SwingWorkerpublish()的输出可以在process()上进行

答案 3 :(得分:0)

不幸的是,事情并非那么简单。只允许GUI线程更新GUI,因此任何其他线程都需要通过SwingUtilities.InvokeLater将任何更新转发到GUI线程。在您的情况下,您可以只包装整个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();