1

I would like some informations about calling swing methods by one thread at once.

EDIT: I use Java 7.

I saw the following topic:

Thread Safety of JTextArea.append

I developped a mini swing application.

This is my main class that is a thread safe class. I call the method SwingUtilities.invokeLater for making it thread safe class.

MainClass :

package swingex;

import javax.swing.SwingUtilities;

public final class MainClass extends Thread {

    public static void main(String[] _args) {
        SwingUtilities.invokeLater(new MainClass());
    }

    @Override
    public void run() {
        new MainWindow();
    }
}

Here is a class inheriting from JFrame. I put a text area and a button in the content pane.

MainWindow:

package swingex;

import java.awt.Dimension;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public final class MainWindow extends JFrame {

    private JTextArea textArea = new JTextArea(32,32);

    //AddText inherits from Thread class
    private AddText thread = new AddText(textArea);

    public MainWindow() {
        JPanel panel_ = new JPanel();
        panel_.setLayout(new BoxLayout(panel_, BoxLayout.PAGE_AXIS));
        JScrollPane scr_ = new JScrollPane(textArea);
        scr_.setPreferredSize(new Dimension(128, 128));
        panel_.add(scr_);
        //The button is used for adding rows in the text area
        JButton button_ = new JButton("Add rows");
        //Adding the event
        button_.addActionListener(new AddTextEvent(this));
        panel_.add(button_);
        setContentPane(panel_);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

    //Called by actionPerformed method
    public void setText() {
        if (thread.isAlive()) {
            //prevent from setting the text area by multi threading
            return;
        }
        //For avoiding issues, the text area is affected by less than two threads.
        thread = new AddText(textArea);
        thread.start();
    }
}

Clicking the button makes sleeping the thread while 5 seconds then the thread adds 200 rows to the text area. AddText:

package swingex;

import java.awt.Rectangle;

import javax.swing.JTextArea;
import javax.swing.text.BadLocationException;

public final class AddText extends Thread {

    private JTextArea textArea;

    public AddText(JTextArea _textArea) {
        textArea = _textArea;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException _0) {
            _0.printStackTrace();
        }
        //Only one thread can access the code
        for (int i = 0; i < 200; i++) {
            textArea.append("Text"+i+"\n");
        }
        int endPosition_ = textArea.getDocument().getLength();
        Rectangle bottom_;
        try {
            bottom_ = textArea.modelToView(endPosition_);
            textArea.scrollRectToVisible(bottom_);
        } catch (BadLocationException _0) {
            _0.printStackTrace();
        }
    }
}

This class implements ActionListener, it is used for clicking button.

AddTextEvent:

package swingex;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public final class AddTextEvent implements ActionListener {

    private MainWindow window;

    public AddTextEvent(MainWindow _window) {
        window = _window;
    }

    @Override
    public void actionPerformed(ActionEvent _e) {
        window.setText();
    }

}

Thank you in advance.

EDIT 2: my "new thread" class is the following one: (The other classes remain the same ones.)

package swingex;

import java.awt.Rectangle;

import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.text.BadLocationException;

/**Now the class AddText inherits from SwingWorker*/
public final class AddText extends SwingWorker<Void, Void> {

    private JTextArea textArea;

    public AddText(JTextArea _textArea) {
        textArea = _textArea;
    }


    /**The equivalent in the class Thread*/
    public void start() {
        execute();
    }

    @Override
    public Void doInBackground() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException _0) {
            _0.printStackTrace();
        }
        for (int i = 0; i < 200; i++) {
            textArea.append("Text"+i+"\n");
        }
        int endPosition_ = textArea.getDocument().getLength();
        Rectangle bottom_;
        try {
            bottom_ = textArea.modelToView(endPosition_);
            textArea.scrollRectToVisible(bottom_);
        } catch (BadLocationException _0) {
            _0.printStackTrace();
        }
        return null;
    }

    /**The equivalent in the class Thread*/
    public boolean isAlive() {
        return !isDone() || !isCancelled();
    }
}

EDIT 3: it is a custom class inheriting from a "swing" component:

Are my custom methods "thread-safe" or not?

MyTextArea:

package swingex;

import javax.swing.JTextArea;

public class MyTextArea extends JTextArea {

    private String aField = "";

    public String getaField() {
        return aField;
    }

    public void setaField(String _aField) {
        aField = _aField;
    }
}

Now, I use the method "invokeLater" of SwingUtilities from the "doInBackground" method of SwingWorker

Community
  • 1
  • 1
cardman
  • 89
  • 3
  • 8
  • 1
    Why not use a Swing `Timer`? – Catalina Island Nov 22 '16 at 12:59
  • @Catalina Island: I wanted to simulate a long treatment by using the method "sleep" of the class Thread. – cardman Nov 22 '16 at 13:03
  • 1
    You could use a `SwingWorker` or `invokeLater()`. – Catalina Island Nov 22 '16 at 13:06
  • No, `doInBackground` will be called in a backgroud thread and this is where you do your long treatment (like sleep...), so it doesn't block the UI. You should update your UI in the `done` method. Check the document in Holger's comment. – grape_mao Nov 23 '16 at 09:00
  • @ grape_mao: I saw Marek Blotny's answer: [What is meant by thread safe code ?](http://stackoverflow.com/questions/261683/what-is-meant-by-thread-safe-code), if only two or more threads call the method append, then there may be issue. – cardman Nov 23 '16 at 13:41
  • @cardman You should take a look at the [threads in Swing](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/). – grape_mao Nov 24 '16 at 10:44

1 Answers1

1

I think you should do actions in Swing's EDT. So everything after the sleep should be called with invokeLater.

This is not a problem of concurrent access. We are not supposed to modify UI in other threads.

What you could do is:

  1. not create your own thread for this action
  2. wrap actions in a Runnable and call it with invokeLater. Then the right order will be guaranteed in EDT.

For long time actions, take a look at swingworker.

And why there is an issue? There are at least 2 threads:

  1. Swing EDT: where you create all your components and Swing modifies its UI.
  2. Thread in background: where you do long treatment and try to append text. And here you access objects in the EDT thread.
grape_mao
  • 1,153
  • 1
  • 8
  • 16
  • I used the method isAlive of the class Thread at the begin of the "click" event, so two threads inheriting from my class "AddText" cannot access simultaneously the "append" method of JTextArea. – cardman Nov 22 '16 at 13:00
  • @cardman: excluding your own invocation of `append` does not prevent the user from typing into the textarea and even if it was read-only, Swing might still try to paint it right at the time when your background action manipulates it. That’s simply broken. Your code doesn’t become valid, if you just copy it into a `SwingWorker`. You should use it as intended, which is explained en detail in [its documentation](http://docs.oracle.com/javase/7/docs/api/?javax/swing/SwingWorker.html). – Holger Nov 22 '16 at 15:29
  • @cardman updated my answer. Anyway, read the Swing documents first and do things as it recommends. – grape_mao Nov 24 '16 at 10:59