Question
I'm trying to understand how threads works in java. This is a simple database request that returns a ResultSet. I'm using JavaFx.
package application;
import java.sql.ResultSet;
import java.sql.SQLException;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class Controller{
@FXML
private Button getCourseBtn;
@FXML
private TextField courseId;
@FXML
private Label courseCodeLbl;
private ModelController mController;
private void requestCourseName(){
String courseName = "";
Course c = new Course();
c.setCCode(Integer.valueOf(courseId.getText()));
mController = new ModelController(c);
try {
ResultSet rs = mController.<Course>get();
if(rs.next()){
courseCodeLbl.setText(rs.getString(1));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// return courseName;
}
public void getCourseNameOnClick(){
try {
// courseCodeLbl.setText(requestCourseName());
Thread t = new Thread(new Runnable(){
public void run(){
requestCourseName();
}
}, "Thread A");
t.start();
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
This returns an exception:
Exception in thread "Thread A" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread A
How do I correctly implement threading so that every database request is executed in a second thread instead of the main thread?
I've heard of implementing Runnable but then how do I invoke different methods in run method?
Never worked with threading before but I thought it's time for it.
Answer
Threading Rules for JavaFX
There are two basic rules for threads and JavaFX:
- Any code that modifies or accesses the state of a node that is part of a scene graph must be executed on the JavaFX application thread. Certain other operations (e.g. creating new
Stage
s) are also bound by this rule. - Any code that may take a long time to run should be executed on a background thread (i.e. not on the FX Application Thread).
The reason for the first rule is that, like most UI toolkits, the framework is
written without any synchronization on the state of elements of the scene
graph. Adding synchronization incurs a performance cost, and this turns out to
be a prohibitive cost for UI toolkits. Thus only one thread can safely access
this state. Since the UI thread (FX Application Thread for JavaFX) needs to
access this state to render the scene, the FX Application Thread is the only
thread on which you can access "live" scene graph state. In JavaFX 8 and
later, most methods subject to this rule perform checks and throw runtime
exceptions if the rule is violated. (This is in contrast to Swing, where you
can write "illegal" code and it may appear to run fine, but is in fact prone
to random and unpredictable failure at arbitrary time.) This is the cause
of theIllegalStateException
you are seeing: you are calling
courseCodeLbl.setText(...)
from a thread other than the FX Application
Thread.
The reason for the second rule is that the FX Application Thread, as well as being responsible for processing user events, is also responsible for rendering the scene. Thus if you perform a long-running operation on that thread, the UI will not be rendered until that operation is complete, and will become unresponsive to user events. While this won't generate exceptions or cause corrupt object state (as violating rule 1 will), it (at best) creates a poor user experience.
Thus if you have a long-running operation (such as accessing a database) that
needs to update the UI on completion, the basic plan is to perform the long-
running operation in a background thread, returning the results of the
operation when it is complete, and then schedule an update to the UI on the UI
(FX Application) thread. All single-threaded UI toolkits have a mechanism to
do this: in JavaFX you can do so by calling Platform.runLater(Runnable r)
to
execute r.run()
on the FX Application Thread. (In Swing, you can call
SwingUtilities.invokeLater(Runnable r)
to execute r.run()
on the AWT event
dispatch thread.) JavaFX (see later in this answer) also provides some higher-
level API for managing the communication back to the FX Application Thread.
General Good Practices for Multithreading
The best practice for working with multiple threads is to structure code that
is to be executed on a "user-defined" thread as an object that is initialized
with some fixed state, has a method to perform the operation, and on
completion returns an object representing the result. Using immutable
objects,
in particular, a
record
,
for the initialized state and computation result is highly desirable. The idea
here is to eliminate the possibility of any mutable state being visible from
multiple threads as far as possible. Accessing data from a database fits this
idiom nicely: you can initialize your "worker" object with the parameters for
the database access (search terms, etc). Perform the database query and get a
result set, use the result set to populate a collection of domain objects, and
return the collection at the end.
In some cases it will be necessary to share mutable state between multiple threads. When this absolutely has to be done, you need to carefully synchronize access to that state to avoid observing the state in an inconsistent state (there are other more subtle issues that need to be addressed, such as liveness of the state, etc). The strong recommendation when this is needed is to use a high-level library to manage these complexities for you.
Using the javafx.concurrent API
JavaFX provides a [concurrency
API](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/package-
summary.html) that is designed for executing code in a background thread, with
API specifically designed for updating the JavaFX UI on completion of (or
during) the execution of that code. This API is designed to interact with the
[java.util.concurrent
API](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-
summary.html), which provides general facilities for writing multithreaded
code (but with no UI hooks). The key class in javafx.concurrent
is
Task
,
which represents a single, one-off, unit of work intended to be performed on a
background thread. This class defines a single abstract method, call()
,
which takes no parameters, returns a result, and may throw checked exceptions.
Task
implements Runnable
with its run()
method simply invoking call()
.
Task
also has a collection of methods which are guaranteed to update state
on the FX Application Thread, such as
[updateProgress(...)
](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#updateProgress-
long-long-),
[updateMessage(...)
](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#updateMessage-
java.lang.String-), etc. It defines some observable properties (e.g.
state
and
value
):
listeners to these properties will be notified of changes on the FX
Application Thread. Finally, there are some convenience methods to register
handlers
([setOnSucceeded(...)
](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#setOnSucceeded-
javafx.event.EventHandler-),
[setOnFailed(...)
](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#setOnFailed-
javafx.event.EventHandler-), etc); any handlers registered via these methods
will also be invoked on the FX Application Thread.
So the general formula for retrieving data from a database is:
- Create a
Task
to handle the call to the database. - Initialize the
Task
with any state that is needed to perform the database call. - Implement the task's
call()
method to perform the database call, returning the results of the call. - Register a handler with the task to send the results to the UI when it is complete.
- Invoke the task on a background thread.
For database access, I strongly recommend encapsulating the actual database code in a separate class that knows nothing about the UI (Data Access Object design pattern). Then just have the task invoke the methods on the data access object.
So you might have a DAO class like this (note there is no UI code here):
public class WidgetDAO {
// In real life, you might want a connection pool here, though for
// desktop applications a single connection often suffices:
private Connection conn ;
public WidgetDAO() throws Exception {
conn = ... ; // initialize connection (or connection pool...)
}
public List<Widget> getWidgetsByType(String type) throws SQLException {
try (PreparedStatement pstmt = conn.prepareStatement("select * from widget where type = ?")) {
pstmt.setString(1, type);
ResultSet rs = pstmt.executeQuery();
List<Widget> widgets = new ArrayList<>();
while (rs.next()) {
Widget widget = new Widget();
widget.setName(rs.getString("name"));
widget.setNumberOfBigRedButtons(rs.getString("btnCount"));
// ...
widgets.add(widget);
}
return widgets ;
}
}
// ...
public void shutdown() throws Exception {
conn.close();
}
}
Retrieving a bunch of widgets might take a long time, so any calls from a UI class (e.g a controller class) should schedule this on a background thread. A controller class might look like this:
public class MyController {
private WidgetDAO widgetAccessor ;
// java.util.concurrent.Executor typically provides a pool of threads...
private Executor exec ;
@FXML
private TextField widgetTypeSearchField ;
@FXML
private TableView<Widget> widgetTable ;
public void initialize() throws Exception {
widgetAccessor = new WidgetDAO();
// create executor that uses daemon threads:
exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
}
// handle search button:
@FXML
public void searchWidgets() {
final String searchString = widgetTypeSearchField.getText();
Task<List<Widget>> widgetSearchTask = new Task<List<Widget>>() {
@Override
public List<Widget> call() throws Exception {
return widgetAccessor.getWidgetsByType(searchString);
}
};
widgetSearchTask.setOnFailed(e -> {
widgetSearchTask.getException().printStackTrace();
// inform user of error...
});
widgetSearchTask.setOnSucceeded(e ->
// Task.getValue() gives the value returned from call()...
widgetTable.getItems().setAll(widgetSearchTask.getValue()));
// run the task using a thread from the thread pool:
exec.execute(widgetSearchTask);
}
// ...
}
Notice how the call to the (potentially) long-running DAO method is wrapped in
a Task
which is run on a background thread (via the accessor) to prevent
blocking the UI (rule 2 above). The update to the UI
(widgetTable.setItems(...)
) is actually executed back on the FX Application
Thread, using the Task
's convenience callback method
[setOnSucceeded(...)
](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#setOnSucceeded-
javafx.event.EventHandler-) (satisfying rule 1).
In your case, the database access you are performing returns a single result, so you might have a method like
public class MyDAO {
private Connection conn ;
// constructor etc...
public Course getCourseByCode(int code) throws SQLException {
try (PreparedStatement pstmt = conn.prepareStatement("select * from course where c_code = ?")) {
pstmt.setInt(1, code);
ResultSet results = pstmt.executeQuery();
if (results.next()) {
Course course = new Course();
course.setName(results.getString("c_name"));
// etc...
return course ;
} else {
// maybe throw an exception if you want to insist course with given code exists
// or consider using Optional<Course>...
return null ;
}
}
}
// ...
}
And then your controller code would look like
final int courseCode = Integer.valueOf(courseId.getText());
Task<Course> courseTask = new Task<Course>() {
@Override
public Course call() throws Exception {
return myDAO.getCourseByCode(courseCode);
}
};
courseTask.setOnSucceeded(e -> {
Course course = courseTask.getCourse();
if (course != null) {
courseCodeLbl.setText(course.getName());
}
});
exec.execute(courseTask);
The API docs for
Task
have many more examples, including updating the progress
property of the
task (useful for progress bars..., etc.
Related