摘要:關于線程池中的疑問對于線程池,相信大家都或多或少使用過。所以,阻塞隊列的作用是控制線程池中線程的生命周期。也就是說,在的線程池,只有消費者使用了阻塞的方法,生產者并沒有。
關于線程池中BlockingQueue的疑問
對于Java線程池,相信大家都或多或少使用過。關于其用法和原理介紹,網上已經有很多非常精彩的文章,珠玉在前,我就不獻丑了。不了解的,可以參考這篇文章。今天我想講的,是關于我對Java線程次的兩個疑問,當然高手可以略過了。
為什么使用阻塞隊列?1.為什么線程池要使用BlockingQueue,而不是ArrayList或別的什么列表?
2.既然使用了BlockingQueue,為什么還要設置拒絕策略,隊列滿的時候不是阻塞嗎?
要回答這個問答,首先來看看不用線程池的時候怎么執行異步任務
new Thread(() -> { // do something }).start();
也就是說,每次需要執行異步任務的時候,新建一個線程去執行,執行完就回收了。這會導致什么問題呢,首先,是對資源的浪費,線程的創建需要陷入內核,需要分配棧空間,需要執行調度,等等,只使用一次就回收太浪費資源。其次,當異步任務比較多的時候,這種方式要創建大量的線程,這對于內存資源也是一個很大的開銷。我們知道,在jvm啟動的時候可以設置線程棧大小的參數-Xss,默認的大小是1M,如果同時啟動1000個線程,就要占用1G的內存,可想而知,這對內存是一個多大的開銷。而且,線程數太多,對于內核的調度壓力也是相當大的,而且,因為頻繁的上下文切換而使程序的局部性喪失,也是一種消耗。線程池的作用,就是線程的復用,那么,怎么復用呢,來看一段代碼:
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
在ThreadPoolExecutor中,線程封裝在Worker中,Worker實現了Runnable,同時在run()方法中調用上面的runWorker()方法,只要runWorker()方法沒有執行完,這個線程就不會被回收。而runWorker()方法要執行下去,就要保證while (task != null || (task = getTask()) != null)的條件為真,第一次判斷時task為firstTask,即執行的第一個任務,那么要點就成了getTask()必須不能為空,來看看getTask()的實現:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
核心邏輯是:
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
這里的workQueue就是阻塞隊列,timed表示是否會超時釋放,keepAliveTime是非核心線程允許的空閑時間;如果不超時,則調用BlockingQueue.take(),如果取不到值,就會一直阻塞直到程序提交了一個任務。所以,阻塞隊列的作用是控制線程池中線程的生命周期。
那么,如果不用阻塞隊列,有沒有別的方式可以實現線程池的功能?答案是,有,但是沒必要。比如我們可以使用wait/notify來控制線程的執行和阻塞,但這里使用生產者/消費者模式來實現是一種更優雅的方式。
為什么需要拒絕策略既然使用了阻塞隊列,那添加任務的時候如果隊列滿了不就阻塞了嗎,拒絕策略是干嘛用的?答案是添加任務調用的并不是阻塞的put()方法,而是非阻塞的offer()方法,看一下ThreadPoolExecutor的execute()方法就知道了
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn"t, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
至于為什么這么實現,應該是不希望阻塞用戶進程吧。
也就是說,在Java的線程池,只有消費者使用了阻塞的方法,生產者并沒有。
SynchronousQueue不過也有例外,調用ExecutorService executorService = Executors.newCachedThreadPool();
時,BlockingQueue的實現類是SynchronousQueue,顧名思義,這是一個同步隊列,其內部沒有容量,使用SynchronousQueue,消費者線程和生產者線程必須交替執行,也就是說,生產者和消費者都必須等待對方就緒。這樣的話,不就阻塞用戶進程了嗎。確實會,但是這個時間非常短,因為使用這種方式,每次通過execute()提交任務的時候,要么復用現有空閑的線程,要么新建一個線程,也就是說線程數理論上沒有上界,所以可以當作不會阻塞
https://stackoverflow.com/questions/7556465/why-threadpoolexecutor-has-blockingqueue-as-its-argument?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
http://www.geek-programmer.com/java-blocking-queues-explained/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69078.html
摘要:當活動線程核心線程非核心線程達到這個數值后,后續任務將會根據來進行拒絕策略處理。線程池工作原則當線程池中線程數量小于則創建線程,并處理請求。當線程池中的數量等于最大線程數時默默丟棄不能執行的新加任務,不報任何異常。 spring-cache使用記錄 spring-cache的使用記錄,坑點記錄以及采用的解決方案 深入分析 java 線程池的實現原理 在這篇文章中,作者有條不紊的將 ja...
摘要:任務性質不同的任務可以用不同規模的線程池分開處理。線程池在運行過程中已完成的任務數量。如等于線程池的最大大小,則表示線程池曾經滿了。線程池的線程數量。獲取活動的線程數。通過擴展線程池進行監控。框架包括線程池,,,,,,等。 Java線程池 [toc] 什么是線程池 線程池就是有N個子線程共同在運行的線程組合。 舉個容易理解的例子:有個線程組合(即線程池,咱可以比喻為一個公司),里面有3...
摘要:一個線程池包含很多準備運行的空閑線程,每當執行完畢后,線程不會死亡而是回到線程池準備為下一個請求提供服務。另一個使用線程池的理由是減少并發線程數。創建大量線程會大大降低性能甚至拖垮虛擬機。 【Future的概念 interface Future ,表示異步計算的結果,Future有個get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態,然后會返回結果或者拋出異常...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結。二線程池線程池的作用線程池作用就是限制系統中執行線程的數量。真正的線程池接口是。創建固定大小的線程池。此線程池支持定時以及周期性執行任務的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結。關于線程之前也寫過一篇文章《高級面試題總結—線程池還能這么玩?》 1、什么是線程池:? java.util...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結。二線程池線程池的作用線程池作用就是限制系統中執行線程的數量。真正的線程池接口是。創建固定大小的線程池。此線程池支持定時以及周期性執行任務的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結。關于線程之前也寫過一篇文章《高級面試題總結—線程池還能這么玩?》 1、什么是線程池:? java.util...
閱讀 2112·2023-04-26 00:41
閱讀 1142·2021-09-24 10:34
閱讀 3573·2021-09-23 11:21
閱讀 4031·2021-09-22 15:06
閱讀 1557·2019-08-30 15:55
閱讀 898·2019-08-30 15:54
閱讀 1829·2019-08-30 15:48
閱讀 550·2019-08-29 13:58