簡體   English   中英

用Java清理線程

[英]Cleaning up threads in Java

我有一個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()方法將單個調用傳遞給外部庫。

在這種情況下如何殺死/清理線程?

如果您對外部庫有足夠的了解,例如:

  1. 永遠不會獲得任何鎖;
  2. 永遠不會打開任何文件/網絡連接;
  3. 從來沒有涉及任何I / O,甚至沒有記錄;

那么使用Thread#stop 可能是安全的。 您可以嘗試並進行廣泛的壓力測試。 任何資源泄漏都應該很快就會顯現出來。

我試試看它是否會響應一個Thread.interrupt()。 當然減少你的數據所以它不會永遠運行,但如果它響應一個中斷()那么你就可以免費回家了。 如果他們鎖定任何東西,執行wait()或sleep(),代碼將必須處理InterruptedException,並且作者可能做了正確的事情。 他們可能吞下它並繼續,但有可能他們沒有。

雖然從技術上講你可以調用Thread.stop(),你需要知道關於該代碼的一切,以確定它是否安全並且你不會泄漏資源。 但是,進行這項研究將使您了解如何輕松修改代碼以查找interrupt()。 您幾乎必須擁有源代碼來審核它以確定哪些意味着您可以輕松地做正確的事情並在那里添加檢查,而不需要花費太多的研究來知道調用Thread.stop()是否安全。

另一種選擇是在線程中導致RuntimeException。 嘗試清空它可能具有的引用或關閉一些IO(套接字,文件句柄等)。 通過更改大小或清空數據來修改它所遍歷的數據數組。 你可以做些什么來使它拋出一個異常並且沒有處理它會關閉。

擴展chubbsondubs的答案,如果第三方庫使用一些定義良好的API(如java.util.List或某些庫特定的API)來訪問輸入數據集,你可以包裝你的輸入數據集使用包裝類傳遞給第三方代碼,該包裝類將在設置cancel標志后拋出異常,例如在List.get方法中。

例如,如果您將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. 顯然,如果庫捕獲並處理運行時異常並繼續處理,這將不起作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM