2017-08-09 51 views
0

使用案例:每次需要處理作業時創建一個新線程。Java ExecutorService - 處於等待狀態的線程

當前實現:我正在使用具有固定大小線程池的Executor服務,比如說50.對於每項工作,我都會向執行程序服務提交一個新線程。

問題:一旦作業完成,線程不會死亡並進入等待狀態。 (在sun.misc.unsafe.park等待)

分析:根據此鏈接(WAITING at sun.misc.Unsafe.park(Native Method))和網絡上的其他源,這是一個有效的場景,並且線程進入等待狀態,等待某個任務被給予他們。

問題:從Java任務控制中,我能夠推斷出線程沒有使用任何資源並且沒有死鎖。這很好。但是考慮提交大量作業並且池中的所有50個線程都被實例化的時間框架。之後,即使作業提交率可能下降,所有50個線程都將保持活躍狀態​​。我也無法關閉執行者服務,因爲它需要一直活着等待作業提交。 如果我創建了普通線程,我會發現線程在他們的工作完成後就死掉了。但是在這種情況下,創建線程的最大數量時沒有製表符。因此,在高峯時間,我們可能會遇到創建的線程多於JVM可以處理的情況。

如何以最佳方式處理這種情況。我們應該忽略處於等待狀態的線程還是應該去執行其他任何實現。

我試圖實現的行爲更像是自動縮放。在高峯期間跨越更多的服務器(在這種情況下是線程)。並且在負載不高時終止額外的服務器並保持最低的服務器數量。

+0

這不就是一個對象池的預期行爲? – 11thdimension

+0

它可能是線程池的默認行爲。但就我而言,所有線程在高峯時間都處於活動狀態,在此之後,它們始終處於等待狀態,之後只有少數幾個將被使用。所以想知道是否有任何可以使用的Executor服務的其他設置。或者任何可以解決這種情況的其他實現。 – samo

+0

如果你想爲每個作業創建一個新的線程,爲什麼你完全使用'ExecutorService'?如果你不這樣做,爲什麼你說的第一件事是? – EJP

回答

1

之後,即使作業提交率可能下降,所有50個線程都將處於活動狀態。我也無法關閉執行者服務,因爲它需要一直活着等待作業提交。

...

怎麼能這種情況下的最好的方式來處理。我們應該忽略處於等待狀態的線程還是應該去執行其他任何實現。

我覺得答案是,你應該忽略它們。線程現在非常高效,當然有50個休眠線程不會以任何方式影響應用程序的運行時間。如果你正在討論大量的線程或者一系列不同的線程池,那麼它們會有所不同。

這就是說,如上所述,如果你想讓你的線程超時,那麼你將需要指定一個不同的「核心」數量的線程(應該總是運行多少個)比「最大」(池的最大數量也可以增長)以及線程在退出之前應該開始休眠多久以保持線程數減少到「核心」數。這個問題是,那麼你需要有一個固定大小的工作隊列,否則第二個線程將永遠不會被創建。這是(不幸)如何ThreadPoolExecutor工作。

如果您擁有不同的核心和最大線程數,並且您將大量作業提交到線程池,那麼您需要阻止生產者,否則如果隊列填滿,作業將被隊列拒絕。

喜歡的東西:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, 
    60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_QUEUE_SIZE)); 
// need to say what to do if the queue is full 
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() { 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 
      // this will block the caller if the queue is full 
      executor.getQueue().put(r); 
    } 
}); 
1

使用ThreadPoolExecutor並通過其任何一個構造函數設置其keepAliveTime屬性。

+0

但是我正在使用的Executors.newFixedThreadPool(int n)反過來使用帶有KeepAliveTime爲0的ThreadPoolExecutor。所以我們不必在實現中顯式使用ThreadPoolExecutor。同樣在這種情況下,當keepAliveTime爲零時,我希望線程立即死亡,但情況並非如此。 – samo

1

可以使用ThreadPoolExecutor來完成。然而它不會做你期望的事情。以下構造函數可用於創建ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)

讓我打破它的行爲記錄。提交任務時

  1. 如果poolSize小於corePoolSize,即使存在空閒線程,也會創建新線程。
  2. 如果poolSize等於corePoolSize,則將任務添加到隊列中。在隊列耗盡之前它不會創建新線程。
  3. 如果workQueue用盡,則創建新線程直至poolSize變爲maximumPoolSize
  4. 如果poolSize等於maximumPoolSizeRejectedExecutionException

所以現在假設我們設定的核心大小爲5,最大尺寸爲10,100個提交任務。如果我們使用Executors類創建池對象,則不會發生任何事情。因爲它創建的池使用LinkedBlockingQueue,默認的構造函數將隊列容量設置爲Integer.MAX_VALUE2147483647)。

以下是Executors

public static ExecutorService newFixedThreadPool(int nThreads) { 
    return new ThreadPoolExecutor(nThreads, nThreads, 
            0L, TimeUnit.MILLISECONDS, 
            new LinkedBlockingQueue<Runnable>()); 
} 

默認構造在LinkedBlockingQueue

public LinkedBlockingQueue() { 
    this(Integer.MAX_VALUE); 
} 
public LinkedBlockingQueue(int capacity) { 
... 

選項的代碼來創建ThreadPoolExecutor直接仍然存在,但是這是沒有太大的幫助。讓我們檢查一下。假設我們使用下面的代碼創建了ThreadPoolExecutor對象。

ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); 
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, IDLE_LIFE_TIME, TimeUnit.SECONDS, workQueue); 

其中MAX_QUEUE_SIZE是10.可以提交的最大任務數可以通過下面的公式找到。

MAX_TASKS = MAX_POOL_SIZE + WORK_QUEUE_CAPACITY 

所以,如果最大池大小爲10,工作隊列大小也是10,那麼21日的任務將被拒絕,如果沒有線程是免費的。

請務必記住,它不會給我們預期的行爲。由於線程僅在線程數比corePoolSize多。只有workQueue已用盡,線程池纔會增加超過corePoolSize

所以maxPoolSize是一個故障安全選項,以避免隊列耗盡。而不是相反。最大池大小不是爲了殺死空閒線程。

如果我們設置的隊列大小太小,我們冒任務拒絕的風險。如果我們設置得太高,poolSize將永遠不會穿越corePoolSize。您可以探索ThreadPoolExecutor.setRejectedExecutionHandler。並且將被拒絕的任務另存爲一個單獨的隊列,一旦workQueue.capacity週期性地變爲小於最大容量,該隊列將向workQueue發送任務。但是,這似乎很多工作沒有相同的收益。

相關問題