Java - 停止访问数据库的长时间运行线程

12

我启动了几个线程,但没有它们的引用以便通过信号或其他方式停止。 例如,我无法像这样传递变量 running=false 给这些线程,因为我没有它们的引用,只有它们的名称。

我正在使用一个 ThreadGroup,并且我总是有它的引用。所以我可以做这样的事情。似乎它不起作用。

    Thread[] threads = new Thread[threadGroup.activeCount()];
    int count = threadGroup.enumerate(threads);
    for(int i = 0; i < count; i++){
        threads[i].interrupt();
    }

这是我的帖子示例。

    public void run{

         try{
             //myDAO.getRecords();
             //this takes 30seconds to 60
             //returns about 3 millions of records

         }catch(Exception e){
             //log
         }
    }
当这个线程正在执行的时候,我想在中途停止。 无论如何数据库查询正在运行,但是我想停止获取结果。
即使我调用interrupt(),仍然会得到结果。 有其他方法可以做到这一点吗?或者我做错了什么?最终的任务是通过Java取消长时间运行的SQL查询。
5个回答

12

当一个线程正在等待查询输出时,调用interrupt方法没有任何影响。因为大多数JDBC驱动程序都对此状态免疫。线程仍然会被阻塞,查询将继续执行。

调用cancel方法将会终止连接并终止数据库中执行查询的线程。尽管偶尔这样做是可以的,但它也会中断连接。这可能会带来严重问题,并很快成为瓶颈。

另一种可行的替代方案是获取在数据库端执行存储过程/查询的线程的ID,并调用:

KILL QUERY id;

KILL QUERY终止当前连接正在执行的语句,但保留连接本身。

要知道ID,在过程中,将第一行设置为:SELECT CONNECTION_ID();。可以使用该ID来终止连接。


2
请注意,所描述的查询终止方法仅适用于MySQL。这里有一个Oracle技巧:https://dev59.com/SXRB5IYBdhLWcg3w6bcE - Vadzim

9
如果您的DAO正在使用JDBC并且想要停止正在进行的查询,您可以让另一个线程在语句上调用cancel

void cancel() throws SQLException

Cancels this Statement object if both the DBMS and driver support aborting 
an SQL statement. This method can be used by one thread to
cancel a statement that is being executed by another thread.

Throws:
    SQLException - if a database access error occurs or this method is 
    called on a closed Statement 
    SQLFeatureNotSupportedException - if the JDBC driver does not support
    this method
你可以让run方法委托DAO调用到另一个线程,然后监听中断并调用cancel方法。
这里是某人使用Spring JdbcTemplate取消查询的帖子。所以它对某些人(使用MySQL)有效。
此外,请参阅此答案,了解Oracle如何取消查询

我对此一无所知,但我会去研究一下。 - sura2k
注意:cancel 命令也会终止连接。如果您频繁使用它,那么它会降低性能。 - Jatin
@Jatin:当不需要运行查询时,让其运行也会影响性能。但是意识到连接将会丢失是一件好事。 - Nathan Hughes
@NathanHughes 更好的做法是使用 Kill_query id(如果你能知道 id 的话)。这不会终止连接,只是指示数据库线程停止执行查询(更多信息在下面的答案中提到)。我遇到了这种情况,这似乎是最好的方法。 - Jatin

3

即使我调用了interrupt(),我仍然能够得到结果。有其他方法可以做到这一点吗?或者我做错了什么?

当您的线程被中断后,您需要在run()方法中检查线程是否通过isInterrupted()条件。

我认为interrupt是实现这一功能的最佳方式,因为中断将取消某些阻塞IO和同步请求。一个定制的解决方案无法做到这一点。


我正在做这件事,但我认为可能有更好的方法。 - sura2k
2
我不确定这一点,但我猜测 interrupt 不会解除数据库连接的阻塞。我知道它不会解除套接字连接的阻塞。 - toto2

2

你的线程代码需要捕获InterruptedException异常,并在你的线程上设置中断标志。更多信息请参见JavaSpecialist通讯

  try {
    // ... 
  } 
  catch (InterruptedException ex) {
    Thread.currentThread().interrupt(); // very important
    break;
  }

要中断的线程不能被计算绑定。也就是说,它应该执行网络IO、睡眠等操作,以便捕获和响应 InterruptedException。像 while(1) {} 这样的循环是无法被中断的。


它给我返回了这个编译错误:InterruptedException的catch块无法到达。该异常从try语句体中永远不会被抛出。 - sura2k
我猜你的 DAO 捕获异常(并且很可能无法正确处理这种情况)。 - Brian Agnew
如果有 sleep() 调用,可以捕获 IntrruptedException - sura2k
1
没错。InterruptedException只会中断一小部分操作(比如sleep)。通常情况下,你需要使用**isInterrupted()**方法来检测中断。 - Edward Falk

0
以下代码将中断无限运行的线程,在中断后,线程将被强制停止。
         @Override public void run() 
           {   // infinite loop to process

                   while(true) // your variable 
                   {                              
                      // We've been interrupted: no more processing.
                      if(Thread.currentThread().isInterrupted()){
                         return;
                       }
                   }

            }
         }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接