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

时间:2015-04-08 17:32:03

标签: java multithreading static javafx task

我有一个General类,它有静态方法和静态变量,Hibernate配置设置和方法,在命中数据库后返回List。我正在研究JavaFx,我最近学会了更好地使用Task进行耗时的操作,例如点击数据库获取一长串数据等。 所以,在我的情况下,我创建了一个Task对象,在匿名内部类中编写代码,其中包含用于访问LOGIN凭据的db的代码。 Task必须返回List的实例。

我在静态方法中初始化了一个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;

  }

Exception which i got

但是,在经过多次组合和随机排列之后,对于我来说,通过评论线程代码而不是用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一起为我工作?

将List转换为static并删除Thread实例并使用task.run();仅仅是为了让我的代码能够工作而从我这边尝试不同的数字。我不知道原因。 任何解释都会让我感到非常谦卑。提前谢谢!

1 个答案:

答案 0 :(得分:3)

发生了什么

使用您的任务创建Thread,然后在线程上调用start()会导致task.run()在您创建的主题中以异步方式执行

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

您对线程的代码在其行为中是不确定的。换句话说,您无法单独从代码中预测结果。问题是您从两个不同的线程访问共享的list实例,无法控制访问顺序。以下是发生的事情:

list最初为null

您致电getSelectList()。 在getSelectList()

  1. 您创建了一个任务。
  2. 您可以将任务配置为在任务完成时将list的值设置为查询结果。 (这将发生在FX应用程序线程上。)
  3. 您创建一个将执行任务的新线程。
  4. 启动线程,导致任务异步执行
  5. 您返回list
  6. 的值

    因为任务是异步执行的,所以在getSelectList()到达return语句之前,您无法控制任务是否完成。

    因此,如果getSelectList()在任务完成之前(并且在调用任务的return处理程序之前)到达其onSucceeded语句,则getSelectList()将返回值{在任务更新之前{1}},即它将返回list。这几乎肯定更有可能发生(因为任务正在访问数据库,这很慢),我希望这就是你得到空指针异常的原因。

    如果任务完成并且null到达其onSucceeded语句之前完成其getSelectList()处理程序的调用,那么到{{} 1}}到达return语句,getSelectList()将被更新,并返回任务设置的值。这是极不可能的,即使它发生了,仍然没有实际保证你得到return的“实时”值(由于Java语言规范中关于线程和内存之间关系的一些复杂性)。

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

    如何修复

    首先,除非进行适当的同步,否则应避免从不同的线程访问共享变量(onSucceeded)。同步自己很困难,您通常应该使用高级API来为您管理。 getSelectList() API专为此设计(以及常规list API)。

    我通常避免在数据访问对象类中管理线程(或异常处理),只是让客户端代码在需要时将线程代码中的DAO调用包装起来。

    所以(我不打算使用这个方法Task,因为这通常很糟糕):

    java.util.concurrent

    然后,从您的JavaFX UI代码中,您可以执行

    static

    如果您确实希望DAO方法异步运行,则需要为其提供回调,以便在成功或失败时执行。所以:

    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 ;
        }
    }
    

    现在,您可以通过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();
    

    进一步改进

    1. 您应该使用正确的通用类型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); } } ,而不是 原型,用于安全类型。
    2. 使用Executor而不是自己管理线程创建。
    3. 警告所有代码都是按原样键入的,没有经过测试,因此可能存在拼写错误。它应该给你这个想法。