I'm using multi threading with Java, I have a thread that will post a message into a queue, a blockingQueue which is thread safe, and I have another thread, implementing a GUI with swing.
Everytime I'm checking whether the queue is empty or not, if not, I poll the message and add it to DefaultListModel, but the problem the display is not updated.
I made sure that the message polled is not empty.
This is the code:
For the implementation of the JList
historyListModel = new DefaultListModel<String>();
historyList = new JList<String>(historyListModel);
historyList.setAutoscrolls(true);
JScrollPane historyScroll = new JScrollPane(historyList);
add(historyScroll, BorderLayout.CENTER);
Adding message to the queue
private BlockingQueue<String> messageRes = new LinkedBlockingQueue<>();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String msgReceived = new String(packet.getData(), 0, packet.getLength());
messageRes.add(msgReceived);
The thread to update the display
private class update implements Runnable{
#Override
public void run() {
while (true){
if (!messageRes.isEmpty()){
historyListModel.addElement(messageRes.poll());
}
}
}
}
Then this thread is called in the GUI main window
new Thread(new update()).start();
In order to make some tests, I tried the following code and It worked
private class update implements Runnable{
#Override
public void run() {
while (true){
Thread.sleep(1000)
historyListModel.addElement("hello world);
}
}
}
The previous code allows me to update the display every 1 second.
I tried one more code in order to triangulate the error:
private class update implements Runnable{
#Override
public void run() {
while (true){
if (!messageRes.isEmpty()){
historyListModel.addElement(messageRes.poll());
historyListModel.addElement("hello world);
}
}
}
}
No display was updated with the previous code.
Could anyone propose any explanation to what is happening ?
Thank you.
Swing is not thread-safe and is single-threaded. This means you should not be updating the UI from outside of the context of the Event Dispatching Thread nor should you be executing long-running/blocking calls within its context.
In your case, a SwingWorker would probably be the best solution, as you can poll the queue for new messages and publish them to the UI in a safe manner.
See...
Concurrency in Swing
Worker Threads and SwingWorker
for more details.
One thing I did note during my testing was this...
#Override
public void run() {
while (true){
if (!messageRes.isEmpty()){
historyListModel.addElement(messageRes.poll());
}
}
}
was probably causing the UI to be overloaded, as poll will either return the next element or null if there are none. So it was a "wild loop". Instead, you should probably be using take which will wait till a new value is available.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingWorker;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JList<String> messageList;
private DefaultListModel<String> model;
public TestPane() {
model = new DefaultListModel<>();
messageList = new JList<>(model);
setLayout(new BorderLayout());
add(new JScrollPane(messageList));
Consumer consumer = new Consumer();
Producer producer = new Producer();
ConsumerWoker worker = new ConsumerWoker(consumer, model);
worker.execute();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
while (true) {
consumer.add(producer.next());
Thread.sleep(500);
}
} catch (InterruptedException ex) {
}
}
});
thread.start();
}
}
public class Producer {
private List<String> master = Arrays.asList(new String[]{
"Malachy Keller",
"Ruqayyah Galvan",
"Harris Nunez",
"Nojus Riggs",
"Joan Mercer",
"Lynda Solomon",
"Raiden Fitzpatrick",
"Sebastian Ahmed",
"Jo Short",
"Nabeel Howarth",
"Maira Garrett",
"Patrik Knights",
"Mimi Mcgill",
"Antonina Villanueva",
"Kenya Hyde",
"Aleksander Rigby",
"Hasan Gilmore",
"Jessica Mcculloch",
"Seth Black",
"Marjorie Brewer",
"Elliot Gay",
"Oluwatobiloba Bowman",
"Domonic Saunders",
"Braden Hale",
"Muneeb Rankin",
"Ruby Tapia",
"Iris Hines",
"Afsana Ponce",
"Beverly Soto",
"Presley Bloggs",
"Leopold Goddard",
"Missy Browne",
"Deniz Woodcock",
"Gwion Ferreira",
"Stanley Mccall",
"Jayda Christie",
"Nikhil Plummer",
"Stacy Crosby",
"Cally Henry",
"Lilliana Taylor",
"Dolcie Navarro",
"Merryn Reynolds",
"Annalise Boyce",
"Anaya Cisneros",
"Aimie Piper",
"Celine Pearson",
"Clayton Battle",
"Danielle Briggs",
"Maddison Couch",
"Jorden Keeling",
"Iylah Holmes",
"Bethaney Quintero",
"Dominique Brett",
"Rohit Benjamin",
"Edgar Rodgers",
"Petra Salgado",
"Myrtle Deleon",
"Letitia Sheridan",
"Wasim Chester",
"Leela Simpson",
"Aine Rojas",
"Ava Mclean",
"Jerry Caldwell",
"Fraser Prosser",
"Callum Vang",
"Yasmin Ochoa",
"Gaia Daly",
"Vanessa Mathews",
"Scarlett Brook",
"Rhiann Fox",
"Nansi Cote",
"Dwayne Rowley",
"Junior Lucas",
"Becky Rush",
"Lori Guthrie",
"Safa Reed",
"Merlin Cartwright",
"Misbah Trejo",
"Khaleesi Ellison",
"Lena Wood",
"Bluebell Coffey",
"Sherry Hutton",
"Abi Delacruz",
"Kwabena Bright",
"Anastazja Kumar",
"Bronwyn Huffman",
"Atif Burke",
"Arwen Kirby",
"Bobbie Noble",
"Blane Bauer",
"Zander Sparrow",
"Marius Wormald",
"Rajan Perez",
"Teejay Faulkner",
"Imaani Rodriquez",
"Safaa Middleton",
"Rafael Livingston",
"Oakley Swan",
"Samiya Kim",
"Glen Beasley"
});
private List<String> avaliableMessages;
public Producer() {
avaliableMessages = new ArrayList<>(100);
}
public String next() {
if (avaliableMessages.isEmpty()) {
avaliableMessages.addAll(master);
}
return avaliableMessages.remove(0);
}
}
public class Consumer {
private BlockingQueue<String> messages = new LinkedBlockingDeque<>();
public void add(String message) {
messages.add(message);
}
public String next() throws InterruptedException {
return messages.take();
}
}
public class ConsumerWoker extends SwingWorker<Void, String> {
private AtomicBoolean keepRunning = new AtomicBoolean(true);
private Consumer consumer;
private DefaultListModel model;
public ConsumerWoker(Consumer consumer, DefaultListModel model) {
this.consumer = consumer;
this.model = model;
}
public void stop() {
keepRunning.set(false);
}
public Consumer getConsumer() {
return consumer;
}
#Override
protected Void doInBackground() throws Exception {
while (keepRunning.get()) {
String message = getConsumer().next();
publish(message);
}
return null;
}
#Override
protected void process(List<String> chunks) {
for (String msg : chunks) {
model.addElement(msg);
}
}
}
}
I have problems with Java's Multi-Threading feature, so hopefully somebody can help me....
Here is my problem:
In the JPanel ExamplePanel which is located in the JFrame ExampleFrame I've added a ComponentListener which invokes the startPaint()-Method. This method should work in a new Thread. My Problem is that by resizing the window "former" Threads aren't closed, meanwhile new Threads are added....
So is there a way to resize the JPanel and to close at the same time the "old" threads, so that the number of threads is not growing, when I resize the JPanel?
I have tried something with a boolean exiter-variable, but it do not seemed to work...
here is the code:
package example;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Example2 {
public static void main(String[] args) throws InterruptedException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new ExampleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
class ExampleFrame extends JFrame {
private static final long serialVersionUID = 1L;
ExamplePanel examplePanel = new ExamplePanel();
private Thread t=null;
private class ExamplePanel extends JPanel implements ComponentListener {
private static final long serialVersionUID = 1L;
#Override
public void componentHidden(ComponentEvent e) {
}
#Override
public void componentMoved(ComponentEvent e) {
}
#Override
public void componentResized(ComponentEvent e) {
startPaint();
}
#Override
public void componentShown(ComponentEvent e) {
}
private void startPaint() {
t=new Thread(new Runnable() {
#Override
public void run() {
//System.out.println(Thread.currentThread().getName());
while (true) {
//System.out.println(Thread.activeCount());
}
}
});
t.start();
}
}
public ExampleFrame() {
examplePanel.addComponentListener((ComponentListener) examplePanel);
getContentPane().add(examplePanel);
}
}
if the calculations don't take long don't use an extra Thread.
if you need this extra Thread make sure that it doesn't run forever (no while (true) without returning at some point)
you can always interrupt your running Thread bfore creating the new one
if (t != null && t.isAlive()) {
t.interrupt();
}
and check in the while(true) loop if the Thread is interrupted
if (t.isInterrupted()) {
System.out.println("Thread ended");
return;
}
hope this helps
When I start my application it opens a JFrame (the main window) and a JFilechooser to select an input directory, which is then scanned.
The scan method itself creates a new JFrame which contains a JButton and a JProgressBar and starts a new Thread which scans the selected Directory. Up until this point everything works fine.
Now I change the Directory Path in my Main Window, which calls the scan method again. This time it creates another JFrame which should contain the JProgressBar and the JButton but it shows up empty (The JFrame Title is still set).
update:
minimal example
public class MainWindow
{
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() throws InterruptedException, ExecutionException
{
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.add(_textArea);
_frame.setSize(200, 200);
_frame.setVisible(true);
_textArea.setText(doStuffinBackground());
_progress.dispose();
}
private String doStuffinBackground() throws InterruptedException,
ExecutionException
{
setUpProgressBar();
ScanWorker scanWorker = new ScanWorker();
scanWorker.execute();
return scanWorker.get();
}
private void setUpProgressBar()
{
// Display progress bar
_progress = new ProgressBar();
}
class ProgressBar extends JFrame
{
public ProgressBar()
{
super();
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
setSize(200, 200);
toFront();
setVisible(true);
}
}
class ScanWorker extends SwingWorker<String, Void>
{
#Override
public String doInBackground() throws InterruptedException
{
int j = 0;
for (int i = 0; i < 10; i++)
{
Thread.sleep(1000);
j += 1;
}
return String.valueOf(j);
}
}
public static void main(String[] args) throws InvocationTargetException,
InterruptedException
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
// Start the main controller
try
{
new MainWindow();
}
catch (InterruptedException | ExecutionException e) {}
}
});
}
}
From the basic looks of your scan method, you are blocking the Event Dispatching Thread, when you scan the directory, which is preventing it from updating the UI.
Specifically, you don't seem to truly understand what Callable and FutureTask are actually used for or how to use them properly...
Calling FutureTask#run will call the Callable's call method...from within the current thread context.
Take a look at Concurrency in Swing for more details...
Instead of trying to use FutureTask and Callable in this manner, consider using a SwingWorker, which is designed to do this kind of work (and uses Callable and FutureTask internally)
Have a look at Worker Threads and SwingWorker for more details
Now, before you jump down my throat and tell me that "it works the first time I ran it", that's because you're not starting your UI properly. All Swing UI's should be create and manipulated from within the context of the Event Dispatching Thread. You main method is executed in, what is commonly called, the "main thread", which is not the same as the EDT. This is basically setting up fluke situation in where the first time you call scan, you are not running within the context of the EDT, allowing it to work ... and breaking the single thread rules of Swing in the process...
Take a look at Initial Threads for more details...
I would also consider using a JDialog instead of another frame, even if it's not modal, it makes for a better paradigm for your application, as it really should only have a single main frame.
Updated based on new code
So, basically, return scanWorker.get(); is a blocking call. It will wait until the doInBackground method completes, which means it's block the EDT, still...'
Instead, you should be making use of the publish, process and/or done methods of the SwingWorker
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MainWindow {
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() {
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
_frame.add(new JScrollPane(_textArea));
_frame.setSize(200, 200);;
_frame.setVisible(true);
doStuffinBackground();
}
private void doStuffinBackground() {
// _progress = new ProgressBar();
// ScanWorker scanWorker = new ScanWorker();
// scanWorker.execute();
// return scanWorker.get();
_progress = new ProgressBar();
ScanWorker worker = new ScanWorker(_textArea, _progress);
worker.execute();
_progress.setVisible(true);
}
class ProgressBar extends JDialog {
public ProgressBar() {
super(_frame, "Scanning", true);
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
pack();
setLocationRelativeTo(_frame);
}
}
class ScanWorker extends SwingWorker<List<String>, String> {
private JTextArea textArea;
private ProgressBar progressBar;
protected ScanWorker(JTextArea _textArea, ProgressBar _progress) {
this.textArea = _textArea;
this.progressBar = _progress;
}
#Override
protected void process(List<String> chunks) {
for (String value : chunks) {
textArea.append(value + "\n");
}
}
#Override
public List<String> doInBackground() throws Exception {
System.out.println("...");
int j = 0;
List<String> results = new ArrayList<>(25);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
j += 1;
System.out.println(j);
results.add(Integer.toString(j));
publish(Integer.toString(j));
}
return results;
}
#Override
protected void done() {
progressBar.dispose();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainWindow();
}
});
}
}
I'm trying to separate my Swing GUI from my actual code. In short, I want the user to kick off a process (based on the user's selections); in this case, the JFrame will no longer be needed.
What I couldn't figure out is how to share the user's selection from the GUI.class with the Main.class.
Do you have any advice for me?
Here's my code:
public class Main {
public static void main(String[] args) {
// Show GUI
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
GUI gui = new GUI(templates);
gui.setVisible(true);
}
});
// Kick off a process based on the user's selection
}
}
public class GUI extends JFrame {
private static final long serialVersionUID = 1L;
public GUI(Object[] objects) {
setTitle("GUI");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 350, 100);
setLocationRelativeTo(null);
JPanel cp = new JPanel();
cp.setBorder(new EmptyBorder(10, 10, 10, 10));
setContentPane(cp);
JLabel lbl = new JLabel("Selection:");
cp.add(lbl);
final JComboBox<String> comboBox = new JComboBox<String>(new String[] { "One", "Two", "Three" });
comboBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
// Share the selected item with Main.class
}
});
cp.add(comboBox);
}
}
You could create an object to store the selection result and pass it in to the constructor of the GUI class. Set the selection result in that object before closing the UI and then your Main class could access the value:
public class SelectionResult {
private String selectionResult;
public void setSelectionResult(final String selectionResult) {
this.selectionResult = selectionResult;
}
public String getSelectionResult() {
return this.selectionResult;
}
}
Then, you could modify the GUI constructor like this:
private final SelectionResult selectionResult;
public GUI(Object[] objects, SelectionResult selectionResult) {
this.selectionResult = selectionResult;
...
Create a SelectionResult object in your Main class, and pass it to the constructor of the GUI class. In you GUI class ActionListener, you can then call the setSelectionResult() method with the selected value and that value will be available from the Main class.
You would need to add code to make your main method wait while you are waiting for the value to be set in the UI and then proceed with your logic based on the selection.
A Good way of doing this is use Callback mechanism.
Steps to follow:
create a callback interface
interface Callback {
void execute(Object result);
}
GUI class will implement Callback interface but without providing any implementation
Make GUI class abstract
abstract class GUI extends JFrame implements Callback
Now create an object of GUI class providing actual implementation of Callback interface
Here you can use Anonymous class
GUI gui = new GUI() {
#Override
public void execute(Object result) {
System.out.println("You have selected " + result);
}
};
You can pass any thing in execute() method of Callback.
comboBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
// Share the selected item with Main.class
// Callback
execute(comboBox.getSelectedItem());
}
});
Here Main class is responsible for capturing the response of Callback that is directed by GUI class.
Here is the code:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class Main {
public static void main(String[] args) {
// Show GUI
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
GUI gui = new GUI() {
#Override
public void execute(Object result) {
System.out.println("You have selected " + result);
}
};
gui.setVisible(true);
}
});
// Kick off a process based on the user's selection
}
}
interface Callback {
void execute(Object result);
}
abstract class GUI extends JFrame implements Callback {
private static final long serialVersionUID = 1L;
public GUI() {
setTitle("GUI");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 350, 100);
setLocationRelativeTo(null);
JPanel cp = new JPanel();
cp.setBorder(new EmptyBorder(10, 10, 10, 10));
setContentPane(cp);
JLabel lbl = new JLabel("Selection:");
cp.add(lbl);
final JComboBox comboBox = new JComboBox(new String[] { "One", "Two", "Three" });
comboBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
// Share the selected item with Main.class
execute(comboBox.getSelectedItem());
}
});
cp.add(comboBox);
}
}
I have a function graphics() that creates my JFrame and two JRadioButtons and adds ActionListeners to them. This graphics is called from main() and graphics itself calls game().
public void game() throws Exception
{
jTextArea1.setLineWrap(true);
jTextArea1.setWrapStyleWord(true);
jTextArea1.setText("This is private information.");
jRadioButton1.setVisible(true);
jRadioButton2.setVisible(true);
try {
t.sleep(40000);
repaint();
} catch (InterruptedException e) {
// We've been interrupted: no more messages.
return;
}
After displaying "This is private information." in the text Area, I want the program execution to pause for 40 seconds, or until the user presses the JRadioButton, whichever is earlier. So I added an ActionListener and called t.interrupt() inside it.
private void jRadioButton1ActionPerformed(java.awt.event.ActionEvent evt) {
t.interrupt();
jRadioButton1.setVisible(false);
jRadioButton2.setVisible(false);
//System.out.println(t.interrupted());
jTextArea1.setText("Please wait...");
}
However, even after choosing the JRadioButton which should trigger the interrupt, that does not happen and t.interrupted returns false.
Any help would be appreciated.
Never, ever call Thread.sleep(...) on the Swing event thread as you will freeze the thread and effectively freeze your program. The solution is to consider use of a Swing Timer for the time-dependent portion of your requirement and using a SelectionListener for the JCheckBox or JRadioButton requirement.
For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
public class PausingExecution extends JPanel {
private static final String SELECTED_TEXT = "Snafus are Better!!!";
private static final String UNSELECTED_TEXT = "Fubars Rule!!";
private static final String TIMES_UP = "Time's Up!!!!";
private static final int TIMER_DELAY = 10 * 1000;
private JTextField messageField = new JTextField(UNSELECTED_TEXT, 10);
private JCheckBox checkBox = new JCheckBox("Click Me");
public PausingExecution() {
add(messageField);
add(checkBox);
checkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent iEvt) {
if (iEvt.getStateChange() == ItemEvent.SELECTED) {
messageField.setText(SELECTED_TEXT);
} else {
messageField.setText(UNSELECTED_TEXT);
}
}
});
Timer mySwingTimer = new Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
messageField.setText(TIMES_UP);
checkBox.setEnabled(false);
}
});
mySwingTimer.setRepeats(false);
mySwingTimer.start();
}
private static void createAndShowGui() {
PausingExecution mainPanel = new PausingExecution();
JFrame frame = new JFrame("PausingExecution");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}