It looks like your application data is all bound up inside your user interface objects. If everything you do can be done on the EDT, then you should be OK. You can make direct calls among the objects that need information from each other. Since all of this is on the EDT, it's effectively single-threaded, and there are no race conditions or deadlocks. This, however, can couple various parts of the UI code to each other rather too tightly. In that case you can use some kind of observer or listener pattern as the commenters have suggested. This works fine in a single-threaded, event-driven environment like with AWT/Swing.
But then you say you might want to do this from some thread other than the EDT. That changes everything.
(As an aside, this is the sort of thing that causes people to consider having all your application data bound up inside your UI objects to be an anti-pattern. It makes it quite difficult to get to this data from outside the UI subsystem.)
You could add an observer pattern, but there are restrictions. The UI owns the data, so only the EDT can change the data and fire events to observers. The observers lists need to maintained in a thread-safe fashion. Presumably the observers will want to update data structures in response to events. These data structures will have to be thread-safe, since they're accessed both from the EDT and from your other thread. This approach will work, but you have to do a lot of thinking about which thread is calling which methods, and which data structures have to be made thread-safe.
Assuming you don't want to go this route, let's return to your original question, which was about how to return something from invokeLater
. Doing this would let you keep your data in the UI while allowing other threads to get data out of the UI when they need it. It is possible to do this, with a bit of indirection. It does have risks, though.
Here's the code. This can be called from the "other" (non-EDT) thread.
InfoObj getInfo() {
RunnableFuture<InfoObj> rf = new FutureTask<>(() -> getInfoObjOnEDT());
SwingUtilities.invokeLater(rf);
try {
return rf.get();
} catch (InterruptedException|ExecutionException ex) {
ex.printStackTrace(); // really, you can do better than this
}
}
The trick here is that RunnableFuture
is an interface that is both a Runnable
and a Future
. This is useful because invokeLater
takes only a Runnable
, but Future
can return a value. More specifically, Future
can capture a value returned in one thread and store it until another thread wants to get it. FutureTask
is a convenient, ready-made implementation of RunnableFuture
that takes a Callable
argument, a function that can return a value.
Basically we create a FutureTask
and hand it a bit of code (the Callable
, a lambda expression in this example) that will run on the EDT. This will gather the information we want and return it. We then post this to the EDT using invokeLater
. When the other thread wants the data, it can call get()
immediately or it can hang onto the Future
and call get()
later. There is a small inconvenience in that Future.get()
throws a couple checked exceptions that you have to deal with.
How does this work? If the EDT runs the Callable
first, the return value is stored in the Future
until the other thread calls get()
on it. If the other thread calls get()
first, it's blocked until the value becomes available.
And there's the rub. Astute readers will recognize that this has the same risk as invokeAndWait()
, namely, if you're not careful, you can deadlock the system. This can occur because the thread that calls get()
might block waiting for the EDT to process the event posted by invokeLater
. If the EDT is blocked for some reason -- perhaps waiting for something held by the other thread -- the result is deadlock. The way to avoid this is to be extremely careful when calling something that might block waiting for the EDT. A good general rule is not to hold any locks while calling one of these blocking methods.
For an example of how you can get yourself into trouble with invokeAndWait
or with invokeLater(FutureTask)
, see this question and my answer to it.
If your other thread is entirely decoupled from the UI data structures, this technique should be quite effective.