我试图了解线程如何在Java中工作。这是一个简单的数据库请求,返回一个ResultSet。我正在使用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应用程序线程上不;currentThread =线程A
如何正确实现线程,以便每个数据库请求都在第二个线程而不是主线程中执行?
我听说过实现Runnable,但是如何在run方法中调用不同的方法?
以前从未使用过线程,但我认为是时候了。
JavaFX的线程规则
线程和JavaFX有两个基本规则:
new Stage
)也受此规则约束。Application
线程是唯一可以访问“实时”场景图状态的线程。在JavaFX 8和更高版本中,受此规则约束的大多数方法都会执行检查,并在违反该规则时抛出运行时异常。(这与Swing
相反,在Swing中,你可以编写“非法”代码,并且看起来可能运行良好,但实际上在任意时间都容易出现随机且无法预测的故障。)这是IllegalStateException
你看到的原因:你是courseCodeLbl.setText(...)
从FX Application Thread
之外的其他线程进行调用。第二条规则的原因是,FX Application Thread
不仅负责处理用户事件,还负责渲染场景。因此,如果你在该线程上执行了长时间运行的操作,则在该操作完成之前,UI才会呈现,并且将对用户事件无响应。虽然这不会产生异常或导致损坏的对象状态(如违反规则1那样),但(最多)它会带来不良的用户体验。
因此,如果你有一个长时间运行的操作(例如访问数据库),需要在完成时更新UI,则基本计划是在后台线程中执行长时间运行的操作,并在操作完成时返回操作结果完成,然后在UI(FX应用程序)线程上安排对UI的更新。所有单线程UI工具箱都有执行此操作的机制:在JavaFX中,你可以通过在FX Application Thread上Platform.runLater(Runnable r)
执行调用来实现r.run()
。(在Swing中,你可以调用在AWT事件分配线程上SwingUtilities.invokeLater(Runnable r)
执行r.run()
。)JavaFX(请参见本答案后面的内容)还提供了一些高级API,用于管理与FX Application Thread
的通信。
多线程通用良好实践
使用多个线程的最佳实践是将要在“用户定义”线程上执行的代码构造为具有某种固定状态初始化的对象,并具有执行该操作的方法,并在完成时返回一个对象代表结果。对于初始化状态和计算结果使用不可变的对象是非常理想的。这里的想法是尽可能消除任何可变状态从多个线程可见的可能性。从数据库访问数据非常适合以下习惯用法:你可以使用数据库访问的参数(搜索项等)来初始化“ worker”对象。执行数据库查询并获取结果集,使用该结果集填充域对象的集合,然后最后返回该集合。
在某些情况下,有必要在多个线程之间共享可变状态。当绝对必须这样做时,你需要仔细同步对该状态的访问,以避免观察到状态不一致的状态(还有其他更细微的问题需要解决,例如状态的活跃性等)。强烈建议在需要时使用高级库来为你管理这些复杂性。
使用javafx.concurrent API
JavaFX提供了一个并发API,专门用于在后台线程中执行代码,而该API专门用于在代码执行完成时(或执行期间)更新JavaFX UI。此API旨在与java.util.concurrentAPI
进行交互,该API提供了用于编写多线程代码的常规工具(但没有UI挂钩)。中的键类javafx.concurrent
是Task,它表示打算在后台线程上执行的单个一次性工作单元。此类定义了一个抽象方法,call()该方法不带任何参数,返回结果并可能引发检查的异常。用调用的方法Task实现。Runnablerun()call()Task
还具有被保证更新状态的FX应用程序线程的方法,例如作为一个集合updateProgress(...)
,updateMessage(...)
等,这定义了一些可观察到的性质(例如state
和value
):听众对这些属性将被通知的FX应用程序线程的变化。最后,有一些方便的方法来注册处理程序(setOnSucceeded(...),setOnFailed(...)
等); 通过这些方法注册的所有处理程序也将在FX Application Thread
上调用。
因此,从数据库检索数据的一般公式为:
因此,你可能会有一个这样的DAO类(请注意,这里没有UI代码):
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();
}
}
检索一堆小部件可能会花费很长时间,因此来自UI类(例如,控制器类)的任何调用都应在后台线程上进行调度。控制器类可能如下所示:
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);
}
// ...
}
请注意,对(可能)长时间运行的DAO方法的调用是如何包装在一个Task在后台线程中(通过访问器)运行的中的,以防止阻塞UI(上述规则2)。widgetTable.setItems(...)
实际上,使用Task的便捷回调方法setOnSucceeded(...)
(满足规则1)在FX Application
线程上执行了对UI()的更新。
在你的情况下,你正在执行的数据库访问将返回单个结果,因此你可能具有类似以下的方法
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 ;
}
}
}
// ...
}
然后你的控制器代码看起来像
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);
该API文档Task
有更多的例子,包括更新progress
任务的属性进度条…等(有用
问题内容: 我试图了解线程如何在Java中工作。这是一个简单的数据库请求,返回一个ResultSet。我正在使用JavaFx。 这将返回一个异常: 线程“线程A”中的异常java.lang.IllegalStateException:在FX应用程序线程上不;currentThread =线程A 如何正确实现线程,以便每个数据库请求都在第二个线程而不是主线程中执行? 我听说过要实现Runnable,
我试图理解线程在java中是如何工作的。这是一个返回结果集的简单数据库请求。我正在使用JavaFx。 这将返回一个异常: 线程“thread A”java中出现异常。lang.IllegalStateException:不在FX应用程序线程上;currentThread=ThreadA 如何正确地实现线程,以便在第二个线程而不是主线程中执行每个数据库请求? 我听说过实现Runnable,但如何在r
问题内容: 这可能是一个非常琐碎的问题,但是我很难找到答案: 使用Google Eclipse插件,我想开发一个简单的旧Java应用程序(而不是Web应用程序),该应用程序使用AppEngine进行云存储。 为此,我当然可以简单地创建两个项目,一个包含AppEngine服务器,另一个包含Java应用程序。 但是我想知道是否有可能在Eclipse中设置一个包含服务器和客户端代码的单个项目(例如GWT
问题内容: 我正在编写一个Servlet,该Servlet通过访问和修改数据库中的某些表来处理每个请求。我希望与数据库的连接是线程安全的。我不想为此使用已经存在的库/框架(spring,hibernate等)。 我知道我可以通过以下方式为此使用java的ThreadLocal: 每次调用时,新连接都会添加到对象中,然后在释放连接时将其删除。 这是这样做的正确方法,还是它本身应该扩展类?还是有一种更
问题内容: 在Apache Commons HttpClient的3.x版本中,可以进行multipart / form-data POST请求(2004年的示例)。不幸的是,这在HttpClient的4.0版本中不再可能。 对于我们的核心活动“ HTTP”,多部分内容超出了范围。我们很乐意使用由其他项目维护的多部分代码,但我对此一无所知。几年前,我们曾尝试将多部分代码移至commons编解码器,
我正在开发一个web应用程序,需要使用带有post方法的ajax发送一些数据。所以我有两种选择来发送数据,无论是JSON格式还是查询婴儿车。我不知道该用哪个?以JSON格式发送数据安全吗?