JAVA線程池的建立與使用

  • 爲何要用線程池?

咱們都知道,每一次建立一個線程,JVM後面的工做包括:爲線程創建虛擬機棧、本地方法棧、程序計數器的內存空間(下圖可看出),因此線程過多容易致使內存空間溢出。同時,當頻繁的建立和銷燬線程容易浪費系統的計算能力在資源的回收和申請中。緩存

 

 

另外:建立過多的線程,會致使cpu在線程中的切換時間比處理時間還多,大大下降了系統的吞吐量。所以咱們使用線程池以下好處:安全

  1. 有效控制線程的數量,防止線程數量過多。
  2. 提升線程的利用程度,避免頻繁的建立及銷燬線程。
  3. 有更靈活的線程使用方式及拒絕措施。

再給你們看看阿里開發規約裏面是怎麼說的併發

 

  • 線程的快速示例

我知道大多數人都但願先看看線程池怎麼建立,而後再深刻了解。下面給你們一個demoide

 1         //存聽任務的阻塞隊列
 2         BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(10);
 3         //BasicThreadFactory是本身實現ThreadFactory接口而來
 4         BasicThreadFactory factory = new BasicThreadFactory();
 5         ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 10, 60,
 6                 TimeUnit.SECONDS, queue, factory,
 7                 (Runnable r, ThreadPoolExecutor executor)->{
 8                         System.out.println(executor.getQueue().size()+"消息隊列已滿");
 9                         System.out.println("拒絕服務");
10 
11                 });
ThreadPoolDemo

 

  • 線程池相關概念

  1. 核心線程:若線程池中的線程標記爲核心線程,即便核心線程沒有運行任務,它也不會被銷燬,會一直存在於線程池中,直至線程池被shutdown。
  2. 非核心線程:當線程池中沒有空閒的核心線程時,線程池會建立一個非核心線程,而且非核心線程的必定時間內處於空閒狀態的時候,非核心線程會被銷燬。
  3. 阻塞隊列:阻塞隊列是當線程池中的沒有能用於處理任務的線程時,會把該任務放入阻塞隊列,待有能用於處理的線程時,把任務從隊列取出處理,阻塞隊列的長度能夠設置。
  4. 拒絕服務處理:當線程池中的沒有線程能提供處理,而且阻塞隊列的空間已滿,此時會觸發拒絕服務異常,開發人員能夠根據本身的需求定製不一樣的處理策略。

 

  • 建立線程池的7個參數

通常咱們推薦使用ThreadPoolExecutor()自定義建立線程池,由於這比較靈活切可控。函數

  1. int corePoolSize  核心線程數,即肯定有多少個核心線程。
  2. int maximumPoolSize  最大線程數,即限定線程池中的最大線程數量。
  3. long keepAliveTime  非核心線程的存活時間,配合下面的TimeUnit參數肯定時間。
  4. TimeUnit unit  一個時間類型的枚舉類。有從納秒到天的時間量度,配合上面的keepAliveTime肯定非核心線程的存活時間。
  5. BlockingQueue<Runnable> workQueue   裝載Runnable的阻塞隊列,具體類型能夠本身肯定。
  6. ThreadFactory threadFactory  線程工廠,這是一個函數式接口,裏面只定義了一個newThread(Runnable task)方法,須要本身實現工廠的方法,在這裏咱們能夠對線程進行自定義的初始化,例如給線程設定名字,這樣方便後期的調試。
  7. RejectedExecutionHandler handler   拒絕服務處理,這也是一個函數式接口,咱們須要實現rejectedExecution(Runnable r, ThreadPoolExecutor executor)這個方法,這裏能夠根據需求自定義你但願在處理邏輯。固然Java裏面也有已經定義好的四種策略靜態類。能夠經過ThreadPoolExecutor調用

 

  • Executors中實現的線程池類型

下面介紹的線程池類型,是Jdk幫咱們制定好的策略。可是,有的線程池類型中,要麼存在線程數量無限制、要麼存在阻塞隊列長度無限制,可是這些應該在開發中避免,由於一旦併發太高,會致使大量的對象積壓,致使JVM內存溢出。spa

寫在前面:jdk提供了默認的工廠方法和默認的默認的拒絕處理策略。線程

默認拒絕策略是:不執行並拋出異常設計

默認的工廠方法是:對線程進行安全檢查並命名。調試

 1     static class DefaultThreadFactory implements ThreadFactory {
 2         private static final AtomicInteger poolNumber = new AtomicInteger(1);
 3         private final ThreadGroup group;
 4         private final AtomicInteger threadNumber = new AtomicInteger(1);
 5         private final String namePrefix;
 6 
 7         DefaultThreadFactory() {
 8             SecurityManager s = System.getSecurityManager();
 9             group = (s != null) ? s.getThreadGroup() :
10                                   Thread.currentThread().getThreadGroup();
11             namePrefix = "pool-" +
12                           poolNumber.getAndIncrement() +
13                          "-thread-";
14         }
15 
16         public Thread newThread(Runnable r) {
17             Thread t = new Thread(group, r,
18                                   namePrefix + threadNumber.getAndIncrement(),
19                                   0);
20             if (t.isDaemon())
21                 t.setDaemon(false);
22             if (t.getPriority() != Thread.NORM_PRIORITY)
23                 t.setPriority(Thread.NORM_PRIORITY);
24             return t;
25         }
26     }
defaultFactory

 

 

  1. FixedThreadPool       固定核心線程的線程池。

特色:它的核心線程數量就是最大線程數,因此線程池內的線程永遠不會消亡,它採用了無參數的鏈表阻塞隊列,最大的任務數可達232-1個。所以存在任務積壓致使內存溢出的風險code

   2.  CachedThreadPool   緩存線程池

特色:沒有核心線程,線程池不能知足任務運行時會建立新的線程,線程數量沒有上限。默認的消亡時間爲60秒。值得注意的是:它的阻塞隊列是SynchronousQueue,這是一個沒有存儲性質的阻塞隊列,它的取值操做和放入操做必須是互斥的。根據源碼文檔的解釋,能夠理解爲每當有任務放入時會當即有線程將它取出執行。

  3.  ScheduledThreadPool  固定調度線程池

特色:有固定的核心線程,線程的數量沒有限制,默認存活時間爲60秒。同時支持定時及週期性任務執行

  4. SingleThreadExecutor  單核心線程池

特色:只有一個核心線程,因此能保證任務的串行化執行。

  5. WorkStealingPool  並行執行線程池

特色:在jdk8中實現 線程池。它內部的線程池實現是ForkJoinPool,這是一個能夠同時利用多個線程來執行任務的線程池。無參默認使用CPU數量的線程數執行任務,因爲這個線程池比較複雜,下次專門寫一篇博文用於更新。

 

  • 線程池的調用流程

須要注意的是:線程池設計的流程是先利用核心線程處理、核心線程不能處理即把它放入阻塞隊列,最好才建立線程來執行任務,直到新建線程也失敗才調用拒絕服務處理。

試着理解一下這樣設計的好處。能夠看到,建立線程永遠不是最早想到的辦法,線程池儘可能避免建立線程。由於建立線程須要調用全局鎖來肯定線程的正確建立,同時也由於線程建立和銷燬也須要消耗資源,因此這種方式在最大努力的避免這種狀況的發生。

 

  • 線程池的關閉

雖然在實際的開發中,線程池通常是隨着項目的部署一塊兒存活的,不會常常關閉,可是仍是須要了解如何關閉,怎麼關閉比較安全。

線程池可經過調用線程池的shutdownshutdownNow方法來關閉線程池.
它們的原理是遍歷線程池中的工做線程,而後逐個調用線程的interrupt方法來中斷線程,因此沒法響應中斷的任務可能永遠沒法終止.
可是它們存在必定的區別

  • shutdownNow首先將線程池的狀態設置成STOP,而後嘗試中止全部的正在執行或暫停任務的線程,並返回等待執行任務的列表
  • shutdown只是將線程池的狀態設置成SHUTDOWN狀態,而後中斷全部沒有正在執行任務的線程.

只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true.
當全部的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true.
至於應該調用哪種方法,應該由提交到線程池的任務的特性決定,一般調用shutdown方法來關閉線程池,若任務不必定要執行完,則能夠調用shutdownNow方法.

線程關閉的方法轉載於做者:全網搜索關注JavaEdge
連接:https://www.nowcoder.com/discuss/152050?type=0&order=0&pos=6&page=0

相關文章
相關標籤/搜索