3

My Swing GUI displays a JList of items that are being sequentially removed by a background thread.

Behind the JList is an ArrayDeque<Card>, myHopper, implementing myHopper.getSize() and myHopper.getElementAt(), as per the contract of an AbstractListModel.

The background thread removes items using myHopper.poll().

Not surprisingly, I'm getting AWT array index out of bounds exceptions currently.

What should I be doing to properly synchronize access to myList between the EDT thread and my background thread? I have seen references to Collections.synchronizedList(arrayList) but I don't think that fits my ArrayDeque.

Chap
  • 3,649
  • 2
  • 46
  • 84

4 Answers4

5

Have you tried just using a LinkedBlockingDeque instead of the ArrayDeque?

Maurício Linhares
  • 39,901
  • 14
  • 121
  • 158
  • I was able to substitute it without any complaint from the compiler, and I suspect a linked list might be a better implementation for a Deque than an Array; however, I'm still getting AWT array index out of bounds exceptions. I did find http://stackoverflow.com/questions/3440360/jlist-throws-arrayindexoutofboundsexceptions-randomly/3440436#3440436 which indicates that one should _never_ modify the model on any thread other than EDT. Kind of makes sense, although I can't quite say why. Back to the drawing board.... – Chap Aug 13 '11 at 05:05
3

The short answer to my question appears to be "You can't: you must never attempt to access a Swing component [and that includes its model] from any thread other than the EDT."

This post shows how I eventually solved the problem. A worker thread needs to pull an item from a JList's model, and does so using invokeAndWait() to schedule that work on the EDT, and then waits until that task is done, and then continues.

Using the synchronized LinkedBlockingDeque didn't work, and I suspect that it's because the EDT makes a nonatomic series of calls to the Deque interface when updating the GUI component. Any change to the model between calls, by another thread, could destroy any assumptions of stability that the EDT is making.

(Perhaps that's what's being hinted at by the persistent "Warning: Swing is not thread safe" that appears throughout the Swing documentation.)

Community
  • 1
  • 1
Chap
  • 3,649
  • 2
  • 46
  • 84
1

The following code works well for me, and might give you some ideas.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.Timer;

public class JListDemo {
    public static void main(String[] args) {
        final MyListModel model = new MyListModel();

        // set up a background task to periodically purge items from the list
        java.util.Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                String item = model.poll();
                if (item != null) {
                    System.out.println("Removed " + item + " from list");
                } else {
                    System.out.println("Nothing to remove off list, click 'Add Item' button to add more");
                }
            }
        }, 1000, 2000);

        JList list = new JList(model);

        // Add a button to add new items to the list
        JButton button = new JButton("Add Item");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                model.offer(new Date().toString());
            }
        });

        JFrame frame = new JFrame("JList Demo");
        frame.add(list);
        frame.add(button, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        frame.setVisible(true);
    }

    private static class MyListModel extends DefaultListModel {
        private final ArrayDeque<String> dq = new ArrayDeque<String>();

        public synchronized String poll() {
            String head = dq.poll();
            if (head != null) {
                removeElementAt(0);
            }
            return head;
        }

        public synchronized void offer(String item) {
            dq.offer(item);
            insertElementAt(item, getSize());
            System.out.println("Added " + item + " to list");
        }
    }

}
Binil Thomas
  • 13,699
  • 10
  • 57
  • 70
0

Execute your operations using SwingWorker instead.

http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html

asahin
  • 1
  • I could be wrong, but I don't think that will help. As I am coming to understand it, if _any_ thread other than the EDT modifies the list model (even one that's got concurrency protection), it runs the risk of doing so during a GUI redraw by the EDT. If the redraw requires more than a single access to the list model, then a rogue modification by another thread will probably destroy any assumptions of stability made by the GUI redraw routine. This is just a guess; I wonder if anyone else can shed light... – Chap Aug 13 '11 at 15:29