【问题标题】:Stopping a non-looping Java thread停止非循环 Java 线程
【发布时间】:2012-03-18 13:32:51
【问题描述】:

这可能是我只是误解了我所读到的内容,但是在 Java 中杀死线程的所有示例似乎都表明您必须向线程发出信号以杀死自己;如果没有一些严重的风险,你不能从外面杀死它。问题是,所有关于如何“礼貌地”要求线程终止的示例都有某种循环,因此您所要做的就是在每次迭代时观察一个标志。

所以,我得到的是一个线程,它只需要一段时间(一系列 SQL 查询)。我当然可以在每个步骤之后进行检查,但它们不在循环中,并且我知道没有一种非常优雅的方式来解决这个问题。这是我正在做的一个例子:

new Thread(new Runnable(){
    public void run(){
        //query 1
        Connection conn = db.getConnection();
        Statement s = conn.createStatement();
        ResultSet rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }

        //query 2
        rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }

        //query 3
        rs = s.executeQuery("SELECT ...");
        while(rs.next()){
            //do stuff
        }
    }
}).start();

这是一个示例,我不使用匿名内部类,但它说明我的 run() 方法不能优雅地停止自身。此外,即使我在每一步后检查,如果特定查询需要很长时间才能运行,则此代码在查询完成之前将无法停止。

此代码用于 GUI 应用程序,我真的很想找到一种无需使用 Thread.stop() 即可快速终止线程的好方法。

编辑 - yshavit 的回答很有帮助,因为我不知道 Statement.cancel() 存在。如果您很好奇,我的特定问题的答案是构建一个更抽象的数据库访问类。该类必须创建一个子线程来执行查询并在它运行时循环,检查每次迭代是否当前线程(不是子线程)被中断。如果它确实被中断,它只是调用 Statement.cancel() 并且子线程将抛出异常并死亡。并非所有 JDBC 驱动程序都支持 Statement.cancel(),但 Oracle 11g 支持。

【问题讨论】:

标签: java sql multithreading thread-safety


【解决方案1】:

如果您不想中断线程,唯一的其他选择是以某种方式终止/取消长时间运行的查询。如果您可以异步启动查询,您可以等到结果准备好或者您收到死亡信号但您 cannot call jdbc async

【讨论】:

    【解决方案2】:

    客户端中断一个线程(即在线程外运行的代码):

    threadInstance.interrupt();
    

    检查你的代码运行的线程是否被中断:

    Thread.currentThread().isInterrupted()
    

    另一种选择是尝试睡觉:

    Thread.currentThread().sleep(1)
    

    睡眠的好处在于,如果线程被中断(即 InterruptedException),它会抛出异常。这就是为什么不忽略这些异常很重要。

    您可以使用 Thread.currentThread().isInterrupted() 在您的 while 循环中添加检查,或者您可以检查语句之间的 sleep(1)。我不会在循环中睡觉,因为这真的会减慢您的代码速度。这可能是混合方法最好的地方:

    if( Thread.currentThread().isInterrupted() ) throw new InterruptedException();
    

    然后你在你的 run() 方法的边界捕获它并停止。

    关键是您必须定期检查是否有外部客户端要求您关闭。如果您在一个线程中有 6 个语句。在这些语句之间进行检查将使您的线程退出。

    public run() {
        try {
           doSomething();
           if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
           doNextSomething();
           if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
           doSomeMoreThings();
           if( Thread.currentInstance().isInterrupted() ) throw new InterruptException();
           doYetMoreThings();
        } catch( InterruptedException e ) {
           System.out.println("Duff man going down.");
        }
    }
    

    这样做与将单个检查放入循环中确实没有区别。

    【讨论】:

      【解决方案3】:

      如果您不想从头开始实现自己的 thread.kill() 机制,您可以使用现有的 API,在 ThreadPoolExecutor 中管理您的线程创建,并使用 Future.cancel() 杀死正在运行的线程:

      ThreadPoolExecutor threadPoolExecutor = Executors.newSingleThreadExecutor();
      Runnable longRunningTask = new Runnable();
      
      // submit task to threadpool:
      Future longRunningTaskFuture = threadPoolExecutor.submit(longRunningTask);
      
      ... ...
      // At some point in the future, if you want to kill the task:
      longRunningTaskFuture.cancel(true);
      ... ...
      

      Cancel 方法的行为会根据您的任务运行状态而有所不同,请查看 API 了解更多详细信息。

      【讨论】:

      • 还要检查isCancelled()
      【解决方案4】:

      找出需要一段时间的事情,然后取消它。如果最耗时的是 rs.next() 循环,您可以这样做:

      while(rs.next()){
          if (myVolatileBooleanSaysToStop) {
              return; // or whatever
          }
          //do stuff
      }
      

      如果需要一段时间的是语句,并且您的 JDBC 驱动程序/服务器支持Statement.cancel,那么您可以将您的Statement 发布到第二个线程,该线程负责酌情调用Statement.cancel。我不确定,但我认为这会导致来自驱动程序的SQLException,然后您可以通过某种方式将其识别为来自取消,并进行相应的处理。

      另外,你应该考虑重构一下。您有三块“运行查询,迭代其结果”,可以将其分解为一个方法(然后负责关闭语句等)。

      【讨论】:

      • 许多数据库驱动程序对于 Statement 和 ResultSet 对象来说不是线程安全的,即使 JDBC 规范规定它们应该是线程安全的。
      • 这是我试图避免的,每次迭代时都添加该检查实在是太难看了。另外,它确实无法解决那个长期运行的查询问题。 ResultSet 仅在查询完成后 可用,遗憾的是我没有改进数据库结构以改进查询的选项。
      • @monitorjbl 关于丑陋,实际上没有其他选择。这就是我建议重构代码的原因——它巩固了丑陋。至于查询本身需要很长时间,如果您因为驱动程序无法取消它,那么您可以从您的应用程序中做很多事情。
      • @yshavit 我的实际代码有单独的方法来处理这些,但它不能轻易合并。我可能只需要制作自己的 DAO;我不打算建立这样的基础设施,因为这段代码不会被多次重用,但我想这是整合一切的好借口。谢谢你的建议!
      • @gordy 您必须启动子线程才能使用语句执行查询。然后,它的父级在子级还活着的时候简单地循环,监视 Thread.isInterrupted() 是否为真。如果遇到这种情况,它所要做的就是运行 Statement.cancel()。我可以验证这确实适用于 Oracle 11g JDBC 驱动程序,但 YMMV。
      【解决方案5】:

      一旦 run() 完成,Java 线程将自行关闭。如果你在一个循环中有run(),跳出循环,Thread.run 将结束,线程将死亡。如果我没记错的话,你也可以使用return;

      【讨论】:

        猜你喜欢
        • 2018-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-24
        • 2014-01-05
        • 1970-01-01
        • 2020-08-16
        • 1970-01-01
        相关资源
        最近更新 更多