无法将JavaFx任务委托给静态方法中的新线程

无法将JavaFx任务委托给静态方法中的新线程,java,multithreading,static,javafx,task,Java,Multithreading,Static,Javafx,Task,我有一个普通类,它有静态方法和静态变量,带有Hibernate配置设置,方法在访问数据库后返回列表。我正在从事JavaFx的工作,最近我了解到,将Task用于耗时的操作(如在数据库中查找一长串数据等)会更好。 因此,在我的例子中,我创建了一个Task对象,在匿名内部类中编写了代码,其中包含了为登录凭证点击db的代码。任务必须返回列表的实例 我在静态方法中初始化了一个线程对象,在其构造函数中传递了任务的对象,将守护进程设置为true并启动了线程。但是,在运行应用程序后,我会收到NullPointe

我有一个普通类,它有静态方法和静态变量,带有Hibernate配置设置,方法在访问数据库后返回列表。我正在从事JavaFx的工作,最近我了解到,将Task用于耗时的操作(如在数据库中查找一长串数据等)会更好。 因此,在我的例子中,我创建了一个Task对象,在匿名内部类中编写了代码,其中包含了为登录凭证点击db的代码。任务必须返回列表的实例

我在静态方法中初始化了一个线程对象,在其构造函数中传递了任务的对象,将守护进程设置为true并启动了线程。但是,在运行应用程序后,我会收到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;

  }
我想知道为什么会这样。为什么task.run()和使用静态列表对我有效

将列表转换为静态,删除线程实例并使用task.run();这仅仅是我为使代码正常工作所做的不同尝试的一个数字。我不知道原因。
如果有任何解释,我将不胜荣幸。提前谢谢

发生了什么事

使用任务创建一个
线程
,然后在该线程上调用
start()
,将导致在所创建的线程中异步执行
task.run()

只需调用
task.run()
即可在当前线程中执行
run()
方法

线程的代码在其行为上是不确定的。换句话说,您无法仅从代码预测结果。问题是您正在从两个不同的线程访问共享
列表
实例,对访问顺序没有控制权。发生的情况如下:

list
最初为
null

您可以调用
getSelectList()
。 在
getSelectList()中

  • 您创建了一个任务
  • 将任务配置为在任务完成时将
    list
    的值设置为查询结果。(这将发生在FX应用程序线程上。)
  • 您将创建一个执行任务的新线程
  • 启动线程,导致任务异步执行
  • 返回
    list
  • 因为任务是异步执行的,所以在
    getSelectList()
    到达其
    return
    语句之前,您无法控制任务是否完成

    因此,如果
    getSelectList()
    在任务完成之前(以及在调用任务的
    onSucceeded
    处理程序之前)到达它的
    return
    语句,
    getSelectList()
    将在任务更新它之前返回
    list
    的值,即它将返回
    null
    。这几乎肯定更有可能发生(因为任务正在访问数据库,速度很慢),我想这就是为什么会出现空指针异常

    如果任务在
    getSelectList()
    到达其
    return
    语句之前完成了对其
    onSucceeded
    处理程序的调用,那么在
    getSelectList()
    到达
    return
    语句之前,
    列表将被更新,并返回任务设置的值。这是极不可能的,即使发生这种情况,也不能保证您得到
    列表的“live”值(因为Java语言规范中关于线程和内存之间关系的一些复杂性)

    请注意,如果从FX应用程序线程调用
    getSelectList()
    ,则保证它将返回
    null
    ,因为在
    getSelectList()
    完成之前不可能调用
    onSucceeded
    处理程序(因为这两个方法调用都在同一个线程上运行——FX应用程序线程)

    如何修复它

    首先,除非进行了适当的同步,否则应避免从不同线程访问共享变量(
    list
    )。自己进行同步比较困难,通常应使用高级API来管理该变量。
    任务
    API就是为此而设计的(以及通用的
    java.util.concurrent
    API)

    我通常避免在数据访问对象类中管理线程(或异常处理),只要让客户机代码在线程代码中包装对DAO的调用(如果需要)

    所以(我不打算让这个方法
    静态
    ,因为这通常是可怕的):

    然后,通过JavaFXUI代码,您可以

    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();
    
    进一步改进

  • 您应该使用正确键入的
    列表,而不是
    原始类型,用于类型安全
  • 使用而不是自己管理线程创建

  • 注意事项所有代码都是按原样输入的,没有经过测试,因此可能会有输入错误。不过,这应该会让您了解情况。

    发生了什么事

    使用任务创建一个
    线程
    ,然后在该线程上调用
    start()
    ,将导致在所创建的线程中异步执行
    task.run()

    只需调用
    task.run()
    即可在当前线程中执行
    run()
    方法

    线程中的代码在行为上是不确定的。换句话说,您无法仅从代码预测结果。问题是您正在从两个不同的线程访问共享
    列表
    实例,无法控制访问顺序。下面是发生的情况:

    list
    最初为
    null

    您可以调用
    getSelectList()
    。 在
    getSelectList()中

  • 您创建了一个任务
  • 将任务配置为将
    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 ;
        }
    }
    
    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();
    
    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);
        }
    }
    
    DAO myDAO = ... ;
    String query =  ... ;
    
    myDAO.getSelectList(query, 
        list -> Platform.runLater(() -> {
            // update UI with list ...
        }),
        exc -> Platform.runLater(() -> {
            // handle exception...
        })
    );