繁体   English   中英

在另一个线程上更新DefaultListModel

[英]updating DefaultListModel on another thread

我有一个程序需要更新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();

    }

基本答案是不要

Swing不是线程安全的。

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

有关详细信息,请阅读Swing中的Concurrency

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

  1. 您的main方法应该在SwingUtilities#invokeLater或类似的方法中包装new Frame()调用
  2. 模型更新线程在后台线程上更改模型。 更新模型将大火是由接收到的事件JList ,对其中的JList自我更新(再次,错误的线程上)。

两种可能的解决方

  • 在后台线程上创建一个新的DefaultListModel ,并在EDT上一次性替换它。
  • 不断更新现有模型,但要确保更新发生在EDT上。
  1. 你有Swing中的并发问题

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

  3. 正确的方法可以开始工作者ThreadRunnable#Thread ,或使用SwingWorker

  4. SwingWorker方法的输出可以在EDT上执行publish()process()

不幸的是,事情并非那么简单。 只允许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();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM