2012-05-26 84 views
1

我在查找更多關於如何限制使用ThreadPoolExecutor創建的任務的運行時間的信息。綁定線程運行時間

我想創建一個自毀的例如當時間已過(例如1米)時,線程將自動終止並返回空值。這裏的關鍵是等待線程完成不應該阻塞主線程(在我們的例子中是UI線程)。

我知道我可以使用get方法,但它會阻止我的應用程序。

我正在考慮運行一個額外的內部線程,睡眠1米,然後在主線程中調用中斷。

我附上了一個示例代碼,它看起來像個好主意,但我需要另一雙眼睛告訴我它是否合理。

public abstract class AbstractTask<T> implements Callable<T> { 
private final class StopRunningThread implements Runnable { 
    /** 
    * Holds the main thread to interrupt. Cannot be null. 
    */ 
    private final Thread mMain; 

    public StopRunningThread(final Thread main) { 
     mMain = main; 

    } 
    @Override 
    public void run() { 
     try { 
      Thread.sleep(60 * 1000); 
      // Stop it. 
      mMain.interrupt(); 
     } catch (final InterruptedException exception) { 
      // Ignore. 
     } 
    } 
} 

()調用通過稱爲線程池

public T call() { 
    try { 
     // Before running any task initialize the result so that the user 
     // won't 
     // think he/she has something. 
     mResult = null; 
     mException = null; 
     // Stop running thread. 
     mStopThread = new Thread(new StopRunningThread(
       Thread.currentThread())); 
     mStopThread.start(); 

     mResult = execute(); <-- A subclass implements this one 
    } catch (final Exception e) { 
     // An error occurred, ignore any result. 
     mResult = null; 
     mException = e; 
     // Log it. 
     Ln.e(e); 
    } 
    // In case it's out of memory do a special catch. 
    catch (final OutOfMemoryError e) { 
     // An error occurred, ignore any result. 
     mResult = null; 
     mException = new UncheckedException(e); 
     // Log it. 
     Ln.e(e); 
    } finally { 
     // Stop counting. 
     mStopThread.interrupt(); 
    } 

    return mResult; 
} 

有幾個點,我有點害怕:

  • 如果執行(會發生什麼)有異常之後我的外線會中斷,那麼我就永遠不會發現異常。
  • 內存/ CPU消耗,我正在使用線程池來避免創建新線程。

您是否看到了達到相同功能的更好主意?

回答

1

這樣做會有些牽扯。首先,你需要擴展ThreadPoolExecutor類。你需要重寫「beforeExecute」和「afterExecute」方法。他們會跟蹤線程的開始時間,並在之後進行清理。然後,您需要收割者定期檢查以確定哪些線程需要清理。

本示例使用Map來記錄每個線程何時啓動。 beforeExecute方法填充它,afterExecute方法清除它。有一個TimerTask週期性執行並查看所有當前條目(即所有正在運行的線程),並在所有超過給定時間限制的時間內調用Thread.interrupt()。

請注意,我已經給出了兩個額外的構造函數參數:maxExecutionTime和reaperInterval來控制給定任務的時間,以及檢查要殺死的任務的頻率。爲了簡潔起見,我在這裏省略了一些構造函數。

請記住,您提交的任務必須發揮很好,並允許自己被殺死。這意味着你必須:執行過程中定期

  1. 檢查Thread.currentThread()isInterrupted()。
  2. 儘量避免任何未聲明的阻塞操作 InterruptedException在它的throws子句中。這個 的一個主要例子是InputStream/OutputStream的用法,您可以使用NIO 通道。如果您必須使用這些方法,請在從此操作返回後立即檢查中斷標誌。

public class TimedThreadPoolExecutor extends ThreadPoolExecutor { 
    private Map<Thread, Long> threads = new HashMap<Thread, Long>(); 
    private Timer timer; 

    public TimedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, 
      long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, 
      long maxExecutionTime, 
      long reaperInterval) { 
     super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); 
     startReaper(maxExecutionTime, reaperInterval); 
    } 

    @Override 
    protected void afterExecute(Runnable r, Throwable t) { 
     threads.remove(Thread.currentThread()); 
     System.out.println("after: " + Thread.currentThread().getName()); 
     super.afterExecute(r, t); 
    } 

    @Override 
    protected void beforeExecute(Thread t, Runnable r) { 
     super.beforeExecute(t, r); 
     System.out.println("before: " + t.getName()); 
     threads.put(t, System.currentTimeMillis()); 
    } 

@Override 
protected void terminated() { 
    if (timer != null) { 
     timer.cancel(); 
    } 
    super.terminated(); 
} 

    private void startReaper(final long maxExecutionTime, long reaperInterval) { 
     timer = new Timer(); 
     TimerTask timerTask = new TimerTask() { 
      @Override 
      public void run() { 
       // make a copy to avoid concurrency issues. 
       List<Map.Entry<Thread, Long>> entries = 
         new ArrayList<Map.Entry<Thread, Long>>(threads.entrySet()); 
       for (Map.Entry<Thread, Long> entry : entries) { 
        Thread thread = entry.getKey(); 
        long start = entry.getValue(); 
        if (System.currentTimeMillis() - start > maxExecutionTime) { 
         System.out.println("interrupting thread : " + thread.getName()); 
         thread.interrupt(); 
        } 
       } 
      } 

     }; 
     timer.schedule(timerTask, reaperInterval, reaperInterval); 
    } 

    public static void main(String args[]) throws Exception { 
     TimedThreadPoolExecutor executor = new TimedThreadPoolExecutor(5,5, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(20), 
       1000L, 
       200L); 

     for (int i=0;i<10;i++) { 
      executor.execute(new Runnable() { 
       public void run() { 
        try { 
         Thread.sleep(5000L); 
        } 
        catch (InterruptedException e) { 

        } 
       } 
      }); 
     } 

     executor.shutdown(); 
     while (! executor.isTerminated()) { 
      executor.awaitTermination(1000L, TimeUnit.MILLISECONDS); 
     } 
    } 



} 
+0

當然要注意,提交給該池的所有任務都有相同的超時時間。但是,只有1個額外的線程被創建(這並不是很多),並且從用戶的角度來看,他們仍然只能看到一個ExecutorService。他們不必改變他們提交的Callable。 – Matt

+0

看起來像一個很好的解決方案。關於最後的意見,從javadoc我可以看到isInterrupted()[清除](http://stackoverflow.com/questions/9901649/after-catching-interruptedexception-why-thread-currentthread-中斷),當拋出異常,所以檢查它不會真的有幫助,我是對的嗎? –

+0

另一個快速問題是,線程池中的線程會中斷嗎?在處理中斷線程的Java源代碼中看不到任何東西 –