Reputation: 1
I created a GUI app where 4 separate SwingWorker threads are executing. Their progress is indicated by individual progress bars and a number next to it (updated dynamically). There is also a "Grand Total" label in the bottom, which is supposed to be the sum of all 4 threads' progress. However, the grand total is not calculated correctly due to race conditions. So far I've tried using the syncrhonized
keyword and utilizing SwingWorker's publish()
and process()
methods. Nothing worked. There are also a Pause and Resume buttons which work, but are creating yet a bigger disparity in the "GrandTotal" number.
Here is my Dialog code:
public class ThreadTestDialog extends JDialog {
private JPanel contentPane;
private JButton buttonStart;
private JButton buttonPause;
private JProgressBar progressBar1;
private JProgressBar progressBar2;
private JProgressBar progressBar3;
private JProgressBar progressBar4;
private JButton buttonResume;
private JLabel labelThread1;
private JLabel labelThread2;
private JLabel labelThread3;
private JLabel labelThread4;
private JLabel labelThread1Total;
private JLabel labelThread2Total;
private JLabel labelThread3Total;
private JLabel labelThread4Total;
private JLabel labelGrandTotal;
private JLabel labelGrandTotalValue;
public AppThread thread1 = new AppThread(labelThread1Total, progressBar1, 30, labelGrandTotalValue);
public AppThread thread2 = new AppThread(labelThread2Total, progressBar2, 75, labelGrandTotalValue);
public AppThread thread3 = new AppThread(labelThread3Total, progressBar3, 50, labelGrandTotalValue);
public AppThread thread4 = new AppThread(labelThread4Total, progressBar4, 20, labelGrandTotalValue);
public ThreadTestDialog() {
setContentPane(contentPane);
setModal(true);
getRootPane().setDefaultButton(buttonStart);
buttonStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onStart();
}
});
buttonPause.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onPause();
}
});
buttonResume.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onResume();
}
});
// call dispose() when cross is clicked
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
dispose();
}
});
// call dispose() on ESCAPE
contentPane.registerKeyboardAction(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
}
private void onStart() {
thread1.execute();
thread2.execute();
thread3.execute();
thread4.execute();
}
private void onPause() {
thread1.setFlag(false);
thread2.setFlag(false);
thread3.setFlag(false);
thread4.setFlag(false);
}
private void onResume() {
int tempValue;
tempValue = thread1.getValue();
thread1 = new AppThread(tempValue, labelThread1Total, progressBar1, 30, labelGrandTotalValue);
thread1.execute();
tempValue = thread2.getValue();
thread2 = new AppThread(tempValue, labelThread2Total, progressBar2, 75, labelGrandTotalValue);
thread2.execute();
tempValue = thread3.getValue();
thread3 = new AppThread(tempValue, labelThread3Total, progressBar3, 50, labelGrandTotalValue);
thread3.execute();
tempValue = thread4.getValue();
thread4 = new AppThread(tempValue, labelThread4Total, progressBar4, 20, labelGrandTotalValue);
thread4.execute();
}
public static void main(String[] args) {
ThreadTestDialog dialog = new ThreadTestDialog();
dialog.pack();
dialog.setVisible(true);
System.exit(0);
}
}
Here is my SwingWorker custom class:
import javax.swing.*;
import java.util.List;
public class AppThread extends SwingWorker<Void, Integer> {
private int value=0;
private int sleepTime;
private JLabel label;
private JProgressBar progressBar;
private JLabel grandTotal;
private boolean flag=true;
public AppThread (JLabel label, JProgressBar progressBar, int sleepTime, JLabel grandTotal) {
this.sleepTime = sleepTime;
this.label = label;
this.progressBar = progressBar;
this.grandTotal = grandTotal;
}
public AppThread (int value, JLabel label, JProgressBar progressBar, int sleepTime, JLabel grandTotal) {
this.value = value;
this.sleepTime = sleepTime;
this.label = label;
this.progressBar = progressBar;
this.grandTotal = grandTotal;
}
public Void doInBackground() {
for (int i = value; i <= 100; i++) {
if (!flag) break;
this.value = i;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
label.setText(Integer.toString(value));
progressBar.setValue(value);
progressBar.setStringPainted(true);
grandTotal.setText(Integer.toString(Integer.parseInt(grandTotal.getText()) + 1));
}
return null;
}
public void done() {
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getValue() {
return value;
}
}
}
I will be extremely grateful for any suggestion about how to synchronize the worker threads so that they update the "GrandTotal" label properly.
Upvotes: 0
Views: 129
Reputation: 347194
You're violating thread rules of Swing, updating the UI from outside the context of the Event Dispatching Thread, which SwingWorker
is suppose to help you with.
Avoid passing references of your UI objects to SwingWorker
, instead, either use its process
method to update some state model or it's PropertyChangeListener
support to update the UI indirectly.
Below is a simple example which uses the PropertyChangeListener
support to update the progress bars.
Note, I've decoupled the AppThread
from the UI, so the UI now takes over the responsibility of updating the UI. The example is also dynamically expandable, so you can increase the number of AppThread
s by simply updating the for-loop
which creates them
ThreadTestDialog
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JProgressBar;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ThreadTestDialog extends JDialog {
private JButton buttonStart;
private Map<AppThread, JProgressBar> progressBars = new HashMap<>(4);
private JProgressBar pbGrandTotal;
public ThreadTestDialog() {
setModal(true);
getRootPane().setDefaultButton(buttonStart);
buttonStart = new JButton("Start");
buttonStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onStart();
}
});
// call dispose() when cross is clicked
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
Random rnd = new Random();
for (int index = 0; index < 4; index++) {
AppThread appThread = new AppThread(rnd.nextInt(1000));
appThread.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
AppThread worker = (AppThread)evt.getSource();
String name = evt.getPropertyName();
if (name.equals("progress")) {
JProgressBar pb = progressBars.get(worker);
pb.setValue(worker.getProgress());
} else if (name.equals("done")) {
// Now you can do something when the worker completes...
}
updateTotalProgress();
}
});
JProgressBar pb = new JProgressBar(0, 100);
progressBars.put(appThread, pb);
add(pb, gbc);
}
pbGrandTotal = new JProgressBar(0, progressBars.size() * 100);
add(pbGrandTotal, gbc);
add(buttonStart, gbc);
}
protected void updateTotalProgress() {
int totalProgress = 0;
for (Map.Entry<AppThread, JProgressBar> entry : progressBars.entrySet()) {
totalProgress += entry.getKey().getProgress();
}
pbGrandTotal.setValue(totalProgress);
}
private void onStart() {
for (Map.Entry<AppThread, JProgressBar> entry : progressBars.entrySet()) {
entry.getKey().execute();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
ThreadTestDialog dialog = new ThreadTestDialog();
dialog.pack();
dialog.setVisible(true);
}
});
}
}
AppThread
import javax.swing.SwingWorker;
public class AppThread extends SwingWorker<Void, Integer> {
private int value = 0;
private int sleepTime;
public AppThread(int sleepTime) {
this.sleepTime = sleepTime;
}
public Void doInBackground() {
for (int i = value; i <= 100; i++) {
this.value = i;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
setProgress(value);
}
return null;
}
}
Upvotes: 1