Introduction
Java FX does not allow for the direct manipulation of screen data from a background thread because of memory addressing restrictions.
Only the Java FX foreground thread may make screen changes. Somehow, a background thread must inform the FX foreground of available data to process.
This program demonstrates:
- how to implement a background's need to pass data to the FX foreground
- technique for passing data from the FX foreground to a background thread
- technique to avoid the FX foreground from blocking in the background
- implementation of completely asynchronous foreground/background processing
This program uses the standard Java Scene Builder application to create the single user screen.
The program was developed using the MVC model for Java FX.
When using Java FX, each screen layout is stored in a separate fxml file.
The screen layout for this program is found in SimpleFXBG.fxml.
Java FX programs have a relatively small mainline class segment.
The mainline code class for this program is found in SimpleFXBG.java.
Screen initialization and event handlers use a controller class segment for each screen.
The controller for this program is SimpleFXBGController.java.
Class data for the controller is:
@FXML Button buttonSend;
@FXML Button buttonStop;
@FXML ListView listMsgs;
@FXML TextField textToSend;
private ObservableList<String> msgData;
private SimpleBG simpleBG;
The @FXML
tags are fields associated with the screen and whose properties are specified in the fxml file.
The event handler used to service the buttonSend
control, is handleSendMessage
, and is activated whenever the user wishes to send a message to the background.
The message that will be sent to the background is contained in the textTosend
text property.
The event handler for buttonStop
screen control is used to shutdown the program.
The FX ListView
is used to display messages from the background on the FX screen control.
A FX ListView
control does not directly get modified to add a new line to the control, rather an ObservableList
is modified with each new background message.
At the screen control constructor instantiation, the ObservableList
is bound to the ListView
screen control.
There is not a lot of screen control property initialization because the properties have been previously defined using the Java FX Scene Builder and place in the fxml file.
The screen controller constructor is now simplified to:
msgData = FXCollections.observableArrayList();
listMsgs.setItems(msgData);
simpleBG = new SimpleBG(this);
The constructor creates a FXCollections.observableArrayList()
for display messages from the background thread on the screen.
Next, the FXCollections.observableArrayList()
is bound to the on screen ListView listMsgs
.
Finally, an instantiation of the background class is created and the instantiation of the screen controller is sent to the background class.
The screen controller must be passed to the background so that the background knows where to send background message directed to the FX foreground.
It is worth noting that no queues with possible associated waiting are created in the FX foreground screen controller.
Sending a message from the FX foreground to the background is in the event handler for the Button
event handler:
@FXML
private void handleSendAction(ActionEvent event)
{
int queueDepth = simpleBG.receiveMsg(textToSend.getText());
}
The only action in the event handler is to send the message to the background object created in the FX screen controller constructor.
This call should not block in the background.
When the background has a message directed at the FX foreground, the background invokes the onMessage
of the FX controller.
public void onMessage(String argMsgToDisplay)
{
msgData.add(argMsgToDisplay);
}
The sole action of the onMessage
event handler adds the background message to the observableArrayList
.
Access to this method is why the FX controller instantiation is passed to the background constructor.
All action on the FX foreground controller has been defined now, the background must be examined.
SimpleBG.java
The background is composed of 4 classes:
- SimpleBG.java
- SimpleBGConsumer.java
- SimpleBGProducer.java
- SimpleBGSender.java
SimpleBG.java
SimpleBG
creates the shared resources used in the background.
It also instantiates the other three background classes, passing necessary shared resources to each class.
private final SimpleBGProducer producer;
private final SimpleBGConsumer consumer;
private final SimpleBGSender sender;
private final ArrayBlockingQueue<String> outboundMsgQueue;
private final ArrayBlockingQueue<String> inboundMsgQueue;
To handle queue of messages between the FX foreground and the background, two ArrayBlockingQueue
s are created, one for each direction.
outboundMsgQueue
queues messages from the background destined to the FX foreground while inboundMsgQueue
queues messages in the other direction.
These queues are the first hint of possible thread blocking.
The class constructor requires the FX class controller as discussed earlier in the FX class constructor.
public SimpleBG(SimpleFXBGController argController)
{
inboundMsgQueue = new ArrayBlockingQueue<>(QUEUE_DEPTH, true);
outboundMsgQueue = new ArrayBlockingQueue<>(QUEUE_DEPTH, true);
consumer = new SimpleBGConsumer(inboundMsgQueue);
sender = new SimpleBGSender(argController, outboundMsgQueue);
producer = new SimpleBGProducer(outboundMsgQueue);
}
The two queues are created.
This two objects have the potential for thread blocking.
Each queue is created with a specific maximum length. QUEUE_DEPTH
The queue true
option is specified to guarantee FIFO ordering of messages.
SimpleBGConsumer.java is the background class for consuming messages from the FX foreground.
At creation, the consumer needs to know which shared resource queue to look at to consume message generated in the FX foreground.
SimpleBGSender.java is the background class for sending messages to the FX foreground.
This is background class the event interrupts the FX foreground is the sender and needs to know who the FX controller is and what queue to retrieve message destined for the FX foreground.
SimpleBGProducer.java is the background class that generates background messages destined for the FX foreground.
The producing needs to know the shared resource queue that holds messages it generates that are being directed to the FX foreground.
The other method in SimpleBG
is:
public int receiveMsg(String argInboundMsg)
{
synchronized(inboundMsgQueue)
{
int queueOccupancy;
if ((queueOccupancy = inboundMsgQueue.size()) == QUEUE_DEPTH)
{
return -1;
}
inboundMsgQueue.add(argInboundMsg);
return ++queueOccupancy;
}
}
This method is called by the FX foreground handleStopAction
event handler.
The receiveMsg
is passed in the message from the FX foreground destined to be consumed in the background.
It is important that when this method is called from the FX foreground that this method not block the FX foreground thread.
Trying to put a FX foreground message into an full ArrayBlockingQueue
while causing the thread to block which is exactly what we do not want to happen to the invoking FX foreground thread.
To prevent blocking, somehow, the FX foreground thread must check if the queue is already full.
However, the check for queue full and then adding the message to the queue are separate lines of code and in a multi-threaded environment, the two lines need to be treated as atomic, i.e., not interruptable.
Placing the two lines within a synchronized block prevents on threads from interrupting the two lines.
Now that the lines are in synchronized block, the foreground thread need only wait a possible small time to get exclusive access to the queue.
The FX foreground tests to see if the queue is full.
When full, the FX foreground call is returned with a -1
to indicate the queue is full.
Returning when full prevents a blocking call putting a message in the queue.
If the queue is not full, the message from the FX foreground is placed in the queue of messages to be consumed.
While this method returns a failed (-1
) or the number of items waiting in the queue to be consumed only a successful/non-successful is required.
The FX foreground could perform some action such as an error message popup message if failure is returned.
SimpleBGConsumer.java
The SimpleBGConsumer
uses two local objects:
private final Thread simpleBGConsumerThread;
private final ArrayBlockingQueue<String> inboundMsgQueue;
The other object is which queue holds messages generated by the FX foreground and directed to this background consumer thread.
public SimpleBGConsumer(ArrayBlockingQueue<String> argInboundMsgQueue)
{
inboundMsgQueue = argInboundMsgQueue;
simpleBGConsumerThread = new Thread(this);
simpleBGConsumerThread.start();
}
The consumer background thread consumes any message placed in the consumer queue. The consumer background thread blocks if there is no message in the queue.
This simplistic consumer only displays the FX message.
However, it could be much more complex, e.g., it could send the message across the Internet. turn on or off a device like a light or pump, or perhaps log the message.
To satisfy the usage of the ArrayBlockingQueue
, the InterruptedException
needs to be caught.
@Override
public void run()
{
String inboundMsg;
String text;
while (true)
{
try
{
inboundMsg = inboundMsgQueue.take();
text = String.format("BG:'%s'", inboundMsg);
System.out.println(text);
}
catch (InterruptedException ie)
{
break;
}
}
}
SimpleBGSender.java
The SimpleBGSender
uses three local objects.
private final SimpleFXBGController controller;
private final Thread simpleBGSenderThread;
private final ArrayBlockingQueue<String> outBoundMsgQueue;
controller
is the FX controller object. It was passed into the SimpleBG
constructor from the FX controller and then passed into this class.
This provides the portal for informing the FX foreground when a background message is available.
simpleBGsenderThread
is the background thread created by this class's constructor.
Again, yes, creating a thread in the constructor is poor coding practice.
The other object is which queue to place background generated messages that are directed to FX foreground.
The background thread of this class takes messages from the shared queue outBoundMsgQueue
then sends them to a local method that does the interrupting of the FX foreground with the available background generated message.
This thread blocks waiting on an available message in outBoundMsgQueue
.
public void run()
{
String textToSend;
while(true)
{
try
{
textToSend = outBoundMsgQueue.take();
sendMsgToFX(textToSend);
}
catch (InterruptedException ie)
{
break;
}
}
}
The other local method does the interruption of the FX foreground to pass on the background generated message.
The argument being passed along to the FX foreground must be declared as final to satisfy FX foreground memory accessibility restrictions.
private void sendMsgToFX(final String argMsgToFX)
{
javafx.application.Platform.runLater
(
() ->
{
controller.onMessage(argMsgToFX);
}
);
}
SimpleBGProducer.java
The SimpleBGProducer
uses three local objects.
private final ArrayBlockingQueue<String> outboundMsgQueue;
private final Thread simpleBGProducerThread;
private final int FIVE_SECONDS = 5000;
simpleBGProducerThread
is the background thread created by this class's constructor.
Again, yes, creating a thread in the constructor is poor coding practice.
The outboundMsgQueue
object is which queue to place background generated messages that will be directed to FX foreground.
The final object FIVE_SECONDS
is the wait time in milliseconds between background thread messages.
It is a good coding practice that is used to avoid embedding constants in code.
Similar to SimpleBGConsumer
, this class saves the passed queue and starts a background thread.
public SimpleBGProducer (ArrayBlockingQueue<String> argOutboundMsgQueue)
{
outboundMsgQueue = argOutboundMsgQueue;
simpleBGProducerThread = new Thread(this);
simpleBGProducerThread.start();
}
The background thread is a simple loop that pauses this thread, then generates a new message and places the message in the queue of messages directed to the FX foreground.
Because the queue may block if full, the InterruptedException
must be dealt with.
public void run()
{
String textToSend;
int msgSendNum = 0;
while(true)
{
try
{
Thread.sleep(FIVE_SECONDS);
textToSend = String.format("SM:Number %d", ++msgSendNum);
outboundMsgQueue.add(textToSend);
}
catch (InterruptedException ie)
{
break;
}
}
}
This concludes the Java FX foreground/background application.
It uses a variety of advanced features such as Java FX, multi-threading, and blocking arrays.
Four threads are used, the implicit FX foreground, plus 3 created background threads. consumer, producer, and sender.
This example passes around String
messages but a more complex item could be passed.
Simplistic consumer and producer threads are created, but these can be modified in complexity to meet the needs of the application.
This approach is not limited to a single:
public void onMessage(String argMsgToDisplay)
Additional event handlers for background generated message can be programmed using overloading of unique data types or using different unique names from onMessage
.
An example of method overload could be:
public void onMessage(String argMsgToDisplay)
and:
public void onMessage(MyClassObject argMsgToDisplay)
An example of unique names could be:
public void onMessageToList1(String argMsgToDisplay)
and:
public void onMessageToList2(String argMsgToDisplay)
History
- 2107/06/28: Initial submission