9

我有一个 Java 方法,它对输入集执行两个计算:估计的和准确的答案。估算值总是可以在可靠的时间内廉价地计算出来。有时可以在可接受的时间内计算出准确的答案,有时则不能(先验未知......必须尝试看看)。

我要设置的是一些框架,如果准确的答案花费太长时间(固定超时),则使用预先计算的估计值。我想我会为此使用一个线程。主要的复杂性是计算准确答案的代码依赖于外部库,因此我不能“注入”中断支持。

这个问题的独立测试用例在这里,展示了我的问题:

package test;

import java.util.Random;

public class InterruptableProcess {
    public static final int TIMEOUT = 1000;

    public static void main(String[] args){
        for(int i=0; i<10; i++){
            getAnswer();
        }
    }

    public static double getAnswer(){
        long b4 = System.currentTimeMillis();
        // have an estimate pre-computed
        double estimate = Math.random();

        //try to get accurate answer
        //can take a long time
        //if longer than TIMEOUT, use estimate instead
        AccurateAnswerThread t = new AccurateAnswerThread();
        t.start();

        try{
            t.join(TIMEOUT);
        } catch(InterruptedException ie){
            ;
        }

        if(!t.isFinished()){
            System.err.println("Returning estimate: "+estimate+" in "+(System.currentTimeMillis()-b4)+" ms");
            return estimate;
        } else{
            System.err.println("Returning accurate answer: "+t.getAccurateAnswer()+" in "+(System.currentTimeMillis()-b4)+" ms");
            return t.getAccurateAnswer();
        }

    }

    public static class AccurateAnswerThread extends Thread{
        private boolean finished = false;
        private double answer = -1;

        public void run(){
            //call to external, non-modifiable code
            answer = accurateAnswer();
            finished = true;
        }

        public boolean isFinished(){
            return finished;
        }

        public double getAccurateAnswer(){
            return answer;
        }

        // not modifiable, emulate an expensive call
        // in practice, from an external library
        private double accurateAnswer(){
            Random r = new Random();
            long b4 = System.currentTimeMillis();
            long wait = r.nextInt(TIMEOUT*2);

            //don't want to use .wait() since
            //external code doesn't support interruption
            while(b4+wait>System.currentTimeMillis()){
                ;
            }
            return Math.random();
        }
    }
}

这工作正常输出...

Returning estimate: 0.21007465651836377 in 1002 ms
Returning estimate: 0.5303547292361411 in 1001 ms
Returning accurate answer: 0.008838428149438915 in 355 ms
Returning estimate: 0.7981717302567681 in 1001 ms
Returning estimate: 0.9207406241557682 in 1000 ms
Returning accurate answer: 0.0893839926072787 in 175 ms
Returning estimate: 0.7310211480220586 in 1000 ms
Returning accurate answer: 0.7296754467596422 in 530 ms
Returning estimate: 0.5880164300851529 in 1000 ms
Returning estimate: 0.38605296260291233 in 1000 ms

但是,我有一个非常大的输入集(大约数十亿个项目)来运行我的分析,我不确定如何清理未完成的线程(我不希望它们在背景)。

我知道有充分的理由不推荐使用各种销毁线程的方法。我也知道停止线程的典型方法是使用中断。但是,在这种情况下,我看不到我可以使用中断,因为该run()方法将单个调用传递给外部库。

在这种情况下如何杀死/清理线程?

4

3 回答 3

2

我会尝试看看它是否会响应 Thread.interrupt()。当然减少你的数据,这样它就不会永远运行,但如果它响应一个中断(),那么你就可以回家了。如果他们锁定任何东西,执行 wait() 或 sleep(),代码将不得不处理 InterruptedException,并且作者可能做了正确的事情。他们可能会吞下它并继续,但有可能他们没有。

虽然从技术上讲,您可以调用 Thread.stop(),但您需要了解有关该代码的所有信息,才能确定它是否安全并且不会泄漏资源。但是,进行这项研究将引导您了解如何轻松修改代码以查找 interrupt() 。您几乎必须拥有源代码来审核它以确定这意味着您可以轻松地做正确的事情并在那里添加检查,而无需进行太多研究以了解调用 Thread.stop() 是否安全。

另一种选择是在线程中引发 RuntimeException。尝试清空它可能拥有的引用或关闭一些 IO(套接字、文件句柄等)。通过更改大小来修改它正在遍历的数据数组或将数据清空。您可以采取一些措施来使其引发异常并且未处理并且它将关闭。

于 2013-01-14T18:58:46.643 回答
2

如果您对外部库足够了解,例如:

  1. 从不获取任何锁;
  2. 从不打开任何文件/网络连接;
  3. 从不涉及任何 I/O,甚至不涉及日志记录;

那么在上面使用它可能是安全的Thread#stop。您可以尝试并进行广泛的压力测试。任何资源泄漏都应该尽快显现出来。

于 2013-01-14T18:51:58.217 回答
1

扩展chubbsondubs的答案,如果第三方库使用一些定义明确的API(例如java.util.List或一些特定于库的API)来访问输入数据集,您可以包装您传递给第三方的输入数据集-带有包装类的聚会代码,该类将在设置标志List.get后抛出异常,例如在方法中。cancel

例如,如果您将 a 传递List给您的第三方库,则可以执行以下操作:

class CancelList<T> implements List<T> {
  private final List<T> wrappedList;
  private volatile boolean canceled = false;

  public CancelList(List<T> wrapped) { this.wrappedList = wrapped; }

  public void cancel() { this.canceled = true; }

  public T get(int index) {
    if (canceled) { throw new RuntimeException("Canceled!"); }
    return wrappedList.get(index);
  }

  // Other List method implementations here...
}

public double getAnswer(List<MyType> inputList) {
  CancelList<MyType> cancelList = new CancelList<MyType>(inputList);
  AccurateAnswerThread t = new AccurateAnswerThread(cancelList);
  t.start();

  try{
    t.join(TIMEOUT);
  } catch(InterruptedException ie){
    cancelList.cancel();
  }

  // Get the result of your calculation here...
}

当然,这种方法取决于几件事:

  1. 您必须非常了解第三方代码,才能知道它调用了哪些方法可以通过输入参数进行控制。
  2. 第三方代码需要在整个计算过程中频繁调用这些方法(即,如果它一次将所有数据复制到内部结构中并在那里进行计算,它将无法工作)。
  3. 显然,如果库捕获并处理运行时异常并继续处理,这将不起作用。
于 2013-01-14T19:22:34.963 回答