我正在尝试理解线程在java中是如何工作的。这是一个返回结果集的简单数据库请求。我正在使用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();
}
}
}
这将返回一个异常:
线程“线程A”中出现异常java.lang.IllegalStateException:不在FX应用程序线程上;当前线程=线程A
如何正确地实现线程化,以便每个数据库请求都在第二个线程而不是主线程中执行?
我听说过实现Runnable,但是我如何在run方法中调用不同的方法呢?
以前从未使用过线程,但我认为是时候了。
3条答案
按热度按时间jobtbby31#
JavaFX的线程规则
线程和JavaFX有两个基本规则:
1.任何修改或访问场景图中节点状态的代码必须在JavaFX应用程序线程上执行。某些其他操作(例如创建新的
Stage
)也受此规则约束。1.任何可能需要很长时间运行的代码都应该在后台线程上执行(即不在FX应用程序线程上执行)。
第一个规则的原因是,像大多数UI工具包一样,框架是在没有对场景图的元素状态进行任何同步的情况下编写的。添加同步会导致性能成本,而这对于UI工具包来说是一个令人望而却步的成本。因此,只有一个线程可以安全地访问该状态。由于UI线程(用于JavaFX的FX应用程序线程)需要访问此状态以渲染场景,因此FX应用程序线程是唯一可以访问“实时”场景图形状态的线程。在JavaFX 8及更高版本中,服从此规则的大多数方法执行检查,并且在违反规则时引发运行时异常。(这与Swing相反,在Swing中,您可以编写“非法”代码,但实际上容易在任意时间发生随机且不可预测的故障。)这就是您看到的
IllegalStateException
的原因:您正在从FX应用程序线程以外的线程调用courseCodeLbl.setText(...)
。第二条规则的原因是FX应用程序线程不仅负责处理用户事件,还负责渲染场景。因此,如果您在该线程上执行长时间运行的操作,则UI将在该操作完成之前不会渲染。并且将对用户事件无响应。虽然这不会生成异常或导致对象状态损坏(违反规则1也会这样),它(充其量)会造成糟糕的用户体验。
因此,如果您有一个需要在完成时更新UI的长时间运行的操作(如访问数据库),基本的计划是在后台线程中执行长时间运行的操作,在操作完成时返回操作结果,然后在UI(FX应用程序)线程上调度UI更新。所有单线程UI工具箱都有一种机制来完成此操作:在JavaFX中,您可以通过调用
Platform.runLater(Runnable r)
在FX应用程序线程上执行r.run()
来实现这一点。(在Swing中,您可以调用SwingUtilities.invokeLater(Runnable r)
在AWT事件调度线程上执行r.run()
。)JavaFX(请参阅本答案后面的内容)还提供了一些高级API,用于管理返回FX应用程序线程的通信。多线程的一般良好做法
使用多线程的最佳实践是将在“用户定义”线程上执行的代码构造为一个对象,该对象使用某个固定状态初始化,具有执行操作的方法,并且在完成时返回表示结果的对象。初始化的状态和计算结果。这里的想法是尽可能地消除任何可变状态从多个线程可见的可能性。从数据库访问数据非常适合这个习惯用法:你可以用数据库访问的参数(搜索词等)初始化你的“工作者”对象。2执行数据库查询并得到一个结果集,使用结果集填充域对象的集合,最后返回集合。
在某些情况下,有必要在多个线程之间共享可变状态。当绝对必须这样做时,您需要仔细地同步对该状态的访问,以避免观察到不一致的状态(还有其他更微妙的问题需要解决,例如国家的活力,当需要这样做时,强烈建议使用高级库来为您管理这些复杂性。
使用javafx.concurrent API
JavaFX提供了一个concurrency API,该concurrency API设计用于在后台线程中执行代码,其API专门设计用于在完成以下操作时更新JavaFX UI此API旨在与
java.util.concurrent
API交互,java.util.concurrent
API为编写多线程代码提供了通用工具javafx.concurrent
中的关键类是Task
,它表示要在后台线程上执行的单个、一次性的工作单元。此类定义单个抽象方法call()
,它不接受参数,返回结果,并且可能抛出已检查异常。Task
通过其run()
方法简单地调用call()
来实现Runnable
。Task
还具有保证更新FX应用程序线程上的状态的方法集合,诸如updateProgress(...)
,updateMessage(...)
,它定义了一些可观察的属性(例如state
和value
):这些属性的监听器将被通知FX应用程序线程上的变化。最后,有一些方便的方法来注册处理程序(setOnSucceeded(...)
,setOnFailed(...)
等);通过这些方法注册的任何处理程序也将在FX应用程序线程上被调用。因此,从数据库检索数据的一般公式是:
1.创建一个
Task
来处理对数据库的调用。1.使用执行数据库调用所需的任何状态初始化
Task
。1.实现任务的
call()
方法以执行数据库调用,并返回调用的结果。1.向任务注册一个处理程序,以便在任务完成时将结果发送到UI。
1.在后台线程上调用任务。
对于数据库访问,我强烈建议将实际的数据库代码封装在一个对UI一无所知的单独类(Data Access Object design pattern)中,然后让任务调用数据访问对象上的方法。
因此,您可能有这样一个DAO类(注意,这里没有UI代码):
检索一堆小部件可能需要很长时间,因此任何来自UI类(例如控制器类)的调用都应该在后台线程上调度。
注意对(潜在的)长时间运行的DAO方法的调用是如何 Package 在
Task
中的,Task
在后台线程上运行(通过访问器)以防止阻塞UI(上面的规则2).对UI(widgetTable.setItems(...)
)的更新实际上是在FX应用程序线程上执行的,使用Task
的便利回调方法setOnSucceeded(...)
(满足规则1).在您的情况下,所执行的数据库访问返回单个结果,因此您可以使用如下方法
然后你的控制器代码看起来像
API docs for
Task
有更多的例子,包括更新任务的progress
属性(对进度条等有用)。相关
djmepvbi2#
线程“线程A”中出现异常java.lang.IllegalStateException:不在FX应用程序线程上;当前线程=线程A
例外情况是试图告诉您,您正在尝试访问JavaFX应用程序线程之外的JavaFX场景图。
如果我做不到这一点,我如何使用后台线程?
这是导致类似解决方案的不同方法。
使用Platform.runLater Package 场景图形元素
最简单的方法是将上面的代码行封装在
Plaform.runLater
中,这样它就可以在JavaFX应用程序线程上执行。使用任务
处理这些场景的“更好的方法”是使用Task,它有专门的方法来发回更新。在下面的示例中,我使用
updateMessage
来更新消息。此属性绑定到courseCodeLbl
textProperty。pxyaymoc3#
这与数据库无关。JavaFx与几乎所有GUI库一样,要求您只使用主UI线程来修改GUI。
您需要将数据从数据库传递回主UI线程。请使用Platform.runLater()来安排Runnable在主UI线程中运行。
或者,您可以使用use Task。