无法在静态方法中将 JavaFx 任务委托给新线程
Cannot delegate JavaFx Task to a new Thread in a static method
我有一个通用 class,它具有静态方法和静态变量以及 Hibernate 配置设置和方法,return 在访问数据库后列出这些方法。我正在研究 JavaFx,我最近了解到最好将 Task 用于耗时的操作,例如点击数据库获取一长串数据等。
因此,在我的例子中,我创建了一个 Task 对象,在匿名内部 class 中编写了代码,其中包含用于为 LOGIN 凭证访问数据库的代码。任务必须 return 列表的实例。
我在静态方法中初始化了一个 Thread 对象,在其构造函数中传递了 Task 的对象,将守护进程设置为 true 并启动了 Thread。但是我在 运行 应用程序后收到 NullPointerException..
private static SessionFactory sessionFactory = null;
private static Session session = null;
private static final Transaction transaction = null;
private static final Configuration configuration = null;
private static List list;
public static List getSelectList(final String query)
{
//list = null;
System.err.println("Inside getSelectList");
try{
final Task <List> task= new Task <List> ()
{
@Override
public List call()
{
try
{
System.err.println("Inside call");
session.beginTransaction();
list = session.createQuery(query).list();
System.err.println(list);
session.getTransaction().commit();
return list;
}
catch (Exception e)
{
session.getTransaction().rollback();
e.printStackTrace();
}
System.err.println("Outta try block");
return null;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent t) {
System.err.println("Inside handle");
list = task.getValue();
/* for (Iterator it = list.iterator();it.hasNext(); )
{
System.err.println("Inside Set on succeeded");
//System.err.println( ((Login)it.next()).getUsername());
} */
}
});
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
//list=task.getValue();
}
catch (Exception e) {e.printStackTrace();}
return list;
}
但是,经过多次组合和随机排列后,对 Thread 代码进行注释并用 task.run();
代替它对我有用
/*Thread th = new Thread(task);
th.setDaemon(true);
th.start();*/
task.run();
try
{
session.beginTransaction();
//List locallist = session.createSQLQuery(query).list();
list = session.createSQLQuery(query).list();
session.getTransaction().commit();
return list ;
}
catch (Exception e){
session.getTransaction().rollback();
我想知道为什么会这样。为什么 task.run() 以及使用静态列表对我有用?
将 List 转换为静态并删除 Thread 实例并使用 task.run();只是我这边为使我的代码工作而进行的不同尝试的一些数字。我不知道原因。
对于任何解释,我都会感到非常谦卑。提前致谢!
发生了什么
用你的任务创建一个 Thread
,然后在线程上调用 start()
导致 task.run()
在线程中 异步执行 你创造了。
只需调用task.run()
在当前线程中执行run()
方法。
线程的代码在其行为上是不确定的。换句话说,您不能仅从代码中预测结果会是什么。问题是您正在从两个不同的线程访问共享 list
实例,无法控制访问顺序。这是发生的事情:
list
最初是 null
.
你打电话给getSelectList()
。
在 getSelectList()
:
- 您创建了一个任务。
- 您将任务配置为在任务完成时将
list
的值设置为查询结果。 (这将发生在 FX 应用程序线程上。)
- 您创建一个将执行任务的新线程。
- 你启动线程,导致任务异步执行
- 你return值
list
因为任务是异步执行的,所以您无法控制任务是否在 getSelectList()
到达其 return
语句之前完成。
因此,如果 getSelectList()
在任务完成之前(以及在调用任务的 onSucceeded
处理程序之前)到达其 return
语句,getSelectList()
将 return 在任务更新它之前 list
的值,即它将 return null
。这几乎肯定更有可能发生(因为任务正在访问数据库,这很慢),我想这就是你得到空指针异常的原因。
如果任务恰好完成 并且 在 getSelectList()
到达其 return
语句之前完成其 onSucceeded
处理程序的调用,则当 getSelectList()
到达 return
语句时,list
将被更新并且它 return 是任务设置的值。这是极不可能的,即使它发生了,仍然没有实际保证你得到 list
的 "live" 值(由于 Java 语言规范中关于线程之间关系的一些复杂性和内存)。
请注意,如果您从 FX 应用程序线程调用 getSelectList()
,那么您 保证 它会 return null
,因为在 getSelectList()
完成之前不可能调用 onSucceeded
处理程序(因为这两个方法调用都 运行 在同一线程上 - FX 应用程序线程)。
如何修复
首先,您应该避免从不同的线程访问共享变量 (list
),除非您有适当的同步。同步自己很困难,您通常应该使用高级 API 来为您管理。 Task
API 就是为此设计的(连同一般的 java.util.concurrent
API)。
我通常避免在数据访问对象中管理线程(或异常处理)class,如果需要,只让客户端代码将对 DAO 的调用包装在线程代码中。
所以(我不打算使用这种方法 static
因为这通常很糟糕):
public List getSelectList(String query) throws Exception {
Session session = sessionFactory.createSession();
try {
session.beginTransaction();
List list = session.createQuery(query).list();
session.getTransaction().commit();
return list ;
} catch (Exception e) {
Transaction tx = session.getTransaction();
if (tx != null) {
tx.rollback();
}
throw e ;
}
}
然后,根据您的 JavaFX UI 代码,您可以
DAO myDAO = ... ;
Task<List> task = new Task<List>() {
@Override
public void call() throws Exception {
return myDAO.getSelectList(...);
}
});
task.setOnSucceeded(event -> {
List list = task.getValue();
// use list to update UI...
});
task.setOnFailed(event -> {
Exception exc = task.getException();
// handle exception...
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
如果您真的希望 DAO 方法 运行 异步,您需要为其提供回调,以便在成功或失败时执行。所以:
public void getSelectList(String query,
Consumer<List> succeededHandler,
Consumer<Exception> errorHandler) {
FutureTask<List> futureTask = new FutureTask<>(() -> {
Session session = sessionFactory.getSession();
try {
session.beginTransaction();
List list = session.createQuery(query).list();
session.getTransaction().commit();
return list ;
} catch (Exception e) {
Transaction tx = session.getTransaction();
if (tx != null) {
tx.rollback();
}
throw e ;
}
});
Thread thread = new Thread(futureTask);
thread.setDaemon(true);
thread.start();
try {
List list = futureTask.get();
succeededHandler.accept(list);
} catch (Exception e) {
errorHandler.accept(e);
}
}
现在从你的UI你做这样的事情:
DAO myDAO = ... ;
String query = ... ;
myDAO.getSelectList(query,
list -> Platform.runLater(() -> {
// update UI with list ...
}),
exc -> Platform.runLater(() -> {
// handle exception...
})
);
进一步改进
- 您应该使用正确的通用类型
List
,而不是
原始类型,为了类型安全。
- 使用
Executor
而不是自己管理线程创建。
警告 所有代码都是按原样输入的,没有测试,所以可能会有拼写错误。不过,它应该能给您思路。
我有一个通用 class,它具有静态方法和静态变量以及 Hibernate 配置设置和方法,return 在访问数据库后列出这些方法。我正在研究 JavaFx,我最近了解到最好将 Task 用于耗时的操作,例如点击数据库获取一长串数据等。 因此,在我的例子中,我创建了一个 Task 对象,在匿名内部 class 中编写了代码,其中包含用于为 LOGIN 凭证访问数据库的代码。任务必须 return 列表的实例。
我在静态方法中初始化了一个 Thread 对象,在其构造函数中传递了 Task 的对象,将守护进程设置为 true 并启动了 Thread。但是我在 运行 应用程序后收到 NullPointerException..
private static SessionFactory sessionFactory = null;
private static Session session = null;
private static final Transaction transaction = null;
private static final Configuration configuration = null;
private static List list;
public static List getSelectList(final String query)
{
//list = null;
System.err.println("Inside getSelectList");
try{
final Task <List> task= new Task <List> ()
{
@Override
public List call()
{
try
{
System.err.println("Inside call");
session.beginTransaction();
list = session.createQuery(query).list();
System.err.println(list);
session.getTransaction().commit();
return list;
}
catch (Exception e)
{
session.getTransaction().rollback();
e.printStackTrace();
}
System.err.println("Outta try block");
return null;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent t) {
System.err.println("Inside handle");
list = task.getValue();
/* for (Iterator it = list.iterator();it.hasNext(); )
{
System.err.println("Inside Set on succeeded");
//System.err.println( ((Login)it.next()).getUsername());
} */
}
});
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
//list=task.getValue();
}
catch (Exception e) {e.printStackTrace();}
return list;
}
但是,经过多次组合和随机排列后,对 Thread 代码进行注释并用 task.run();
代替它对我有用 /*Thread th = new Thread(task);
th.setDaemon(true);
th.start();*/
task.run();
try
{
session.beginTransaction();
//List locallist = session.createSQLQuery(query).list();
list = session.createSQLQuery(query).list();
session.getTransaction().commit();
return list ;
}
catch (Exception e){
session.getTransaction().rollback();
我想知道为什么会这样。为什么 task.run() 以及使用静态列表对我有用?
将 List 转换为静态并删除 Thread 实例并使用 task.run();只是我这边为使我的代码工作而进行的不同尝试的一些数字。我不知道原因。 对于任何解释,我都会感到非常谦卑。提前致谢!
发生了什么
用你的任务创建一个 Thread
,然后在线程上调用 start()
导致 task.run()
在线程中 异步执行 你创造了。
只需调用task.run()
在当前线程中执行run()
方法。
线程的代码在其行为上是不确定的。换句话说,您不能仅从代码中预测结果会是什么。问题是您正在从两个不同的线程访问共享 list
实例,无法控制访问顺序。这是发生的事情:
list
最初是 null
.
你打电话给getSelectList()
。
在 getSelectList()
:
- 您创建了一个任务。
- 您将任务配置为在任务完成时将
list
的值设置为查询结果。 (这将发生在 FX 应用程序线程上。) - 您创建一个将执行任务的新线程。
- 你启动线程,导致任务异步执行
- 你return值
list
因为任务是异步执行的,所以您无法控制任务是否在 getSelectList()
到达其 return
语句之前完成。
因此,如果 getSelectList()
在任务完成之前(以及在调用任务的 onSucceeded
处理程序之前)到达其 return
语句,getSelectList()
将 return 在任务更新它之前 list
的值,即它将 return null
。这几乎肯定更有可能发生(因为任务正在访问数据库,这很慢),我想这就是你得到空指针异常的原因。
如果任务恰好完成 并且 在 getSelectList()
到达其 return
语句之前完成其 onSucceeded
处理程序的调用,则当 getSelectList()
到达 return
语句时,list
将被更新并且它 return 是任务设置的值。这是极不可能的,即使它发生了,仍然没有实际保证你得到 list
的 "live" 值(由于 Java 语言规范中关于线程之间关系的一些复杂性和内存)。
请注意,如果您从 FX 应用程序线程调用 getSelectList()
,那么您 保证 它会 return null
,因为在 getSelectList()
完成之前不可能调用 onSucceeded
处理程序(因为这两个方法调用都 运行 在同一线程上 - FX 应用程序线程)。
如何修复
首先,您应该避免从不同的线程访问共享变量 (list
),除非您有适当的同步。同步自己很困难,您通常应该使用高级 API 来为您管理。 Task
API 就是为此设计的(连同一般的 java.util.concurrent
API)。
我通常避免在数据访问对象中管理线程(或异常处理)class,如果需要,只让客户端代码将对 DAO 的调用包装在线程代码中。
所以(我不打算使用这种方法 static
因为这通常很糟糕):
public List getSelectList(String query) throws Exception {
Session session = sessionFactory.createSession();
try {
session.beginTransaction();
List list = session.createQuery(query).list();
session.getTransaction().commit();
return list ;
} catch (Exception e) {
Transaction tx = session.getTransaction();
if (tx != null) {
tx.rollback();
}
throw e ;
}
}
然后,根据您的 JavaFX UI 代码,您可以
DAO myDAO = ... ;
Task<List> task = new Task<List>() {
@Override
public void call() throws Exception {
return myDAO.getSelectList(...);
}
});
task.setOnSucceeded(event -> {
List list = task.getValue();
// use list to update UI...
});
task.setOnFailed(event -> {
Exception exc = task.getException();
// handle exception...
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
如果您真的希望 DAO 方法 运行 异步,您需要为其提供回调,以便在成功或失败时执行。所以:
public void getSelectList(String query,
Consumer<List> succeededHandler,
Consumer<Exception> errorHandler) {
FutureTask<List> futureTask = new FutureTask<>(() -> {
Session session = sessionFactory.getSession();
try {
session.beginTransaction();
List list = session.createQuery(query).list();
session.getTransaction().commit();
return list ;
} catch (Exception e) {
Transaction tx = session.getTransaction();
if (tx != null) {
tx.rollback();
}
throw e ;
}
});
Thread thread = new Thread(futureTask);
thread.setDaemon(true);
thread.start();
try {
List list = futureTask.get();
succeededHandler.accept(list);
} catch (Exception e) {
errorHandler.accept(e);
}
}
现在从你的UI你做这样的事情:
DAO myDAO = ... ;
String query = ... ;
myDAO.getSelectList(query,
list -> Platform.runLater(() -> {
// update UI with list ...
}),
exc -> Platform.runLater(() -> {
// handle exception...
})
);
进一步改进
- 您应该使用正确的通用类型
List
,而不是 原始类型,为了类型安全。 - 使用
Executor
而不是自己管理线程创建。
警告 所有代码都是按原样输入的,没有测试,所以可能会有拼写错误。不过,它应该能给您思路。