I have multiple worker threads, and a JavaFX GUI which is reporting on what is happening in these threads.
There is a lot of data shared between threads and it needs to be visualized. So I'm using ObservableList's and Property's to be able to easily show the data in JavaFX.
I've made a small example app to show something similar to what happens in my application.
It has 2 lists and the worker thread moves data from one list to the other. A status String is kept up to date.
Full example code can be found at http://codetidy.com/6569/ (this code will crash, see later)
Here are the shared ObservableList's & Properties:
private ObservableList<String> newItems;
private ObservableList<String> readyItems;
private StringProperty status;
Here's how they are used in JavaFX:
listViewA.setItems(processor.getNewItems());
listViewB.setItems(processor.getReadyItems());
statusLabel.textProperty().bind(processor.getStatus());
The worker thread updates these lists and properties, but off course, it needs to do this on the JavaFX thread, and that's where things get ugly.
This would be the code if I didn't have to update on the JavaFX thread:
Runnable newItemAdder = new Runnable() {
@Override
public void run() {
while(true) {
synchronized (newItems) {
String newItem = checkForNewItem(); //slow
if (newItem != null) {
newItems.add(newItem);
newItems.notify();
}
if (newItems.size() >= 5)
status.set("Overload");
else
status.set("OK");
}
synchronized (readyItems) {
if (readyItems.size() > 10)
readyItems.remove(0);
}
try { Thread.sleep(200); } catch (InterruptedException e) { return; }
}
}
};
new Thread(newItemAdder).start();
Runnable worker = new Runnable() {
@Override
public void run() {
while(true) {
List<String> toProcess = new ArrayList<String>();
synchronized (newItems) {
if (newItems.isEmpty())
try { newItems.wait(); } catch (InterruptedException e) { return; }
toProcess.addAll(newItems);
}
for (String item : toProcess) {
String processedItem = processItem(item); //slow
synchronized (readyItems) {
readyItems.add(processedItem);
}
}
}
}
};
new Thread(worker).start();
Off course, some things are easy to solve with Platform.runLater:
Platform.runLater(new Runnable() {
@Override
public void run() {
synchronized (newItems) {
if (newItems.size() >= 5)
status.set("Overload");
else
status.set("OK");
}
}
});
That's fine for properties/lists that I only write to in the task, and only read in the JavaFX GUI.
But it gets very complicated to do it for the lists in this example, on which you need to synchronize, read and write. You need add a lot of Platform.runLater and you need to block until the "runLater" task has finished. This results in very complex and hard to read and write code (I managed to get this example running this way, see what I mean: http://codetidy.com/6570/).
Are there any other ways to get my example working? I'd appreciate any other solution or partial solutions…
Best Answer
Background Info
Task javadoc includes numerous concurrency usage patterns for passing data between threads in JavaFX.
Task includes convenience data transfer methods such as updateMessage and can be used instead of your Runnable with the user defined status property.
When appropriate, consider using a collection structure designed for concurrency, such as a BlockingQueue. An additional advantage is that BlockingQueues can have size limits, which seems like something you want.
Some general advice
The above are just rules of thumb and don't need to be followed didactically.
Reasonably Complex Threading Samples