My Swing GUI displays a JList of items that are being sequentially removed by a background thread.
Behind the JList is an ArrayDeque<Card>
, my开发者_如何学GoHopper, 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.
Have you tried just using a LinkedBlockingDeque instead of the ArrayDeque?
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.)
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");
}
}
}
Execute your operations using SwingWorker instead.
http://download.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html
精彩评论