Java Swing EDT & Concurrency

2019-05-05 03:54发布

问题:

I was just wondering if it is still necessary to ensure synchronicity in an invokeLater() Runnable.

I am encountering deadlock and need to overcome it while maintaining concurrency.

Would this be an example of good code?:

private String text;

private void updateText()
{
    SwingUtilities.invokeLater(new Runnable()
    {
        public void run()
        {
            synchronized(FrameImpl.this)
            {
                someLabel.setText(text);
            }
        }
    });
}

Sorry for the rather bad example, but we must assume that text is being modified by different threads, cannot be injected, and is reliant on a correct value.

Is this the proper solution or will I unintentionally create a deadlock problem by sending synchronized code off into an unknown context..?

Thanks.

回答1:

A better solution would be something like this:

public class Whatever {
    private String text;
    private final Object TEXT_LOCK = new Object();

    public void setText(final String newText) {
        synchronized (TEXT_LOCK) {
            text = newText;
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                someLabel.setText(newText);
            }
        });
    }

    public String getText() {
        synchronized (TEXT_LOCK) {
            return text;
        }
    }
}

This will ensure that if two threads try to call setText concurrently then they will not clobber each other. The first thread in will set the value of text and enqueue a UI update with that value. The second thread will also set the value of text and enqueue a second UI update.

The end result is that the UI will eventually show the most recent text value, but the internal text variable will immediately contain the most recent value.

A couple of notes:

  1. Using a seperate lock object (i.e. TEXT_LOCK) means you are not vulnerable to code somewhere else locking the monitor on the Whatever instance and inadvertently causing a deadlock. Best to always keep tight control of your lock objects. It's also best to minimize the size of your synchronized blocks.
  2. You could make the whole setText method synchronized, subject to the caveat that it does make you potentially vulnerable to deadlock as above.
  3. Reading the value of text also needs to be synchronized even though Strings are immutable. There are subtleties to the Java memory model that mean you always need to synchronize around variables that can be read/written by multiple threads.

Check out Brian Goetz's Java Concurrency in Practice for a great dive into the tricky parts of concurrency (including the memory model weirdness).



回答2:

now it would be correct, all output from task must be wrapped ito InvokeLater(), another example for update GUI from BackGround task(s) is here

private String text;

private void updateText() {

    synchronized (FrameImpl.this) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                someLabel.setText(text);
            }
        });
    }
}