Java並發編程與高並發之多線程


1、線程池,初始化好線程池的實例以后,將要執行的任務丟到線程池里面,等待任務的調度執行。

2、使用new Thread的弊端。

  弊端一、每次new Thread新建對象,性能差,
  弊端二、線程缺乏統一管理,可以無限制的新建線程,相互競爭,有可能占用過多系統資源導致死機或者OOM。
  弊端三,缺少更多功能,如更多執行,定期執行,線程中斷。

3、使用線程池的好處。

  好處一、重用存在的線程,減少對象創建,消亡的開銷,性能佳。  
  好處二、可以有效的控制最大並發的線程數,提高系統資源的利用率,同時可以避免過多的資源競爭,避免阻塞。
  好處三、提高定時執行、定期執行,單線程,並發數控制等功能。

4、線程池ThreadPoolExecutor

  1)、參數一、corePoolSize,核心線程數量。
  2)、參數二、maximumPoolSize,線程最大線程數。
  3)、參數三、workQueue,阻塞隊列,存儲等待執行的任務,很重要,會對線程池運行過程產生重大影響。

    保存等待任務的阻塞隊列,當提交新的任務到線程池以后,線程池會根據當前線程池中正在運行着的線程的數量來決定該任務的處理方式,處理方式主要有三種,直接切換、使用無界隊列、使用有界隊列。直接切換這種常用的隊列就是阻塞隊列里面的SynchronizedQueue,使用無界隊列是一般使用基於鏈表的阻塞隊列LinkedBlockingQueue,如果使用這種無界隊列的方式呢,線程池中能夠創建的最大線程數是corePoolSize,而maximumPoolSize就不會起作用了,當線程池中所有的核心線程都是運行狀態的時候,一個新的任務提交以后就會放到等待隊列里面去,當workQueue為有界隊列的時候,一般使用的是ArrayBlockingQueue,使用這種方式可以將線程池最大線程數量限制為maximumPoolSize,這樣可以降低資源的消耗,但是,這種方式使得線程池對線程調度變得更困難,因為我們的線程池和隊列容量都是有限的,所以要想使線程池處理任務和吞吐率達到一個相對合理的范圍,又想使線程調度相對簡單,並且還能盡可能降低線程池對資源的消耗,我們需要合理的設置這兩個數量corePoolSize、maximumPoolSize。具體說一下,如果想降低系統資源的消耗,包括CPU的使用率,操作系統資源的消耗,環境切換開銷等等,可以設置一個較大的隊列容量,和較小的線程池容量,這樣會降低線程處理任務的吞吐量。如果我們提交的任務經常發生阻塞,可以考慮調用設置maximumPoolSize,線程最大線程數,重新設置線程池的容量。如果我們的隊列容量設置較小,通常需要將這個線程池容量設置較大一些,這樣CPU的使用率會較高一些。但是如果線程池容量設置過大,在提交任務過多的情況下,並發量會增加,那么線程之間的調度就是一個需要考慮的問題,這樣反而會降低處理任務的吞吐量。

    corePoolSize、maximumPoolSize、workQueue三個參數的關系。
如果運行的線程數少於corePoolSize核心線程數量的時候,直接創建新的線程執行任務,即使線程池中的其他線程是空閑的,如果線程池中的線程大於等於corePoolSize核心線程數量的時候,且小於maximumPoolSize,線程最大線程數的時候,則只有當workQueue滿的時候才創建新的線程處理任務。如果我們設置的corePoolSize和maximumPoolSize相同的時候,那么創建的線程池的大小是固定的,這個時候如果有新的任務提交,如果workQueue還沒有滿的時候,就將請求放入到workQueue中,等待有空閑的線程從workQueue取出任務,進行處理。如果我們運行的線程數量大於maximumPoolSize,此時如果workQueue也已經滿了,通過拒絕策略的參數指定策略來處理任務。所以任務提交的時候判斷順序主要有三個,第一個看corePoolSize,如果corePoolSize小於maximumPoolSize就直接創建線程調用了,接下來看的是workQueue,最后看到的是maximumPoolSize。

 

  4)、參數四、keepAliveTime,線程沒有任務執行最多保持多久時間終止。線程池維護線程所允許的空閑時間,當線程池中的線程數量大於corePoolSize的時候,如果此時沒有新的任務提交,核心線程不會立即銷毀,而是進行等待,直到等待的時間超過keepAliveTime。

  5)、參數五、unit,keepAliveTime的時間單位。
  6)、參數六、threadFactory,線程工廠,用來創建線程。默認有一個默認的工廠創建線程,使用默認的工廠創建線程的時候,會使新創建的工廠具有相同的優先級,並且是非守護的線程,同時也設置了線程的名稱。
  7)、參數七、rejectHanbler,拒絕策略,當拒絕處理任務時候的策略。如果workQueue對應的阻塞隊列滿了,並且沒有空閑的線程池,此時,如果繼續提交任務,就需要采取一種策略來進行處理這個任務,線程池一共提供了四種策略,第一種就是直接拋出異常,也是默認的策略,第二種用調用者所在的線程執行任務,第三種是丟棄線程中最靠前的任務,並執行當前任務,相當於好久之前的任務就不要了,直接丟棄掉,直接執行當前任務,第四種是直接丟棄該任務。

5、線程池中的幾種狀態。如下所示:

 

當我們初始化一個線程池以后,通常有以下幾種狀態,Running、Shutdown、Stop、Tidying、Terminated。這些狀態我們不需要特別處理,是線程池內部根據方法來定義線程池的狀態的,有印象即可。
  1)、Running是判斷接收新提交的任務,並且也能處理阻塞隊列里面的任務。
  2)、Shutdown是屬於關閉狀態,當一個線程池實例處於Shutdown狀態的時候,不能再接收新提交的任務,但是可以處理阻塞隊列中保存的任務,在線程池處於Running時,調用shutdown()方法的時候,會使線程池進入該狀態。
  3)、Stop狀態,當一個線程池實例處於Stop狀態的時候,不能再接收新提交的任務,也不能處理阻塞隊列中保存的任務。中斷正在處理任務的線程,在線程池處於Running或者Shutdown時,調用shutdownNow()方法的時候,會使線程池進入該狀態。
  4)、Tidying狀態,如果所有的任務都已經終止,有效線程數量為0,就進入該狀態了。
  5)、Terminated狀態,之后Tidying狀態調用terminated()方法進入該狀態。

6、線程池ThreadPoolExecutor的方法介紹。

  1)、execute()方法,提交任務,交給線程池執行。當有了線程池實例以后呢,就需要將線程放入到線程池里面的,調度執行,調度方法就是execute方法。
  2)、submit()方法,提交任務,能夠返回執行結果,相當於execute + Future方法的組合。submit方法和execute類似,都是提交任務,但是submit方法可以返回結果,拿到返回結果繼續處理。
  3)、shutdown()方法,關閉線程池,等待任務都執行完。線程池使用完畢以后,記得關閉線程池。
  4)、shutdownNow()方法,關閉線程池,不等待任務執行完。不等待任務執行完,直接關閉掉了,暫停正在執行的線程。使用場景,不得不暫停所有任務,關閉線程池。
  5)、getTaskCount()方法,線程池已經執行和未執行的任務總數。適用於監控的方法。
  6)、getCompletedTaskCount()方法,已經完成的任務數量。適用於監控的方法。
  7)、getPoolSize()方法,線程池當前的線程數量。適用於監控的方法。
  8)、getActiveCount()方法,當前線程池中正在執行任務的線程數量。適用於監控的方法。

7、線程池類圖,如下所示:

Executor框架是根據一組執行策略的調用,調度,執行和控制的異步任務的框架,目的是提高一種將任務提交與任務如何運行分離開的機制。JUC里面有三個Executor接口,分別如下所示:
  1)、Executor,運行先任務的簡單接口。
  2)、ExecutorService,擴展了Executor接口,添加了用來管理執行器生命周期和任務生命周期的方法。
  3)、ScheduledExecutorService,擴展了ExecutorService接口,支持Future和定期執行任務。
ThreadPoolExecutor是功能最強的,可以根據功能需求傳入我們需要的參數,以及指定任何策略。

8、線程池Executor框架接口提供的方法。

  1)、Executors.newCachedThreadPool,命名了一個新的線程池,創建了一個可以緩存的線程池,如果線程池的長度超過了處理的需要,可以靈活回收空閑線程,如果沒有可以回收的,就新建線程。
  2)、Executors.newFixedThreadPool,創建一個定長的線程池,可以控制線程的最大並發數目,超出的線程會在隊列里面等待。
  3)、Executors.newScheduledThreadPool,創建一個定長的線程池,支持定時和周期性的任務執行。
  4)、Executors.newSingleThreadExecutor,創建的是一個單線程化的線程池,只會用唯一的一個工作線程執行任務,保證所有任務按照指定順序執行,可以指定先入先出,優先級等等進行執行。

注意:線程池的合理配置,CPU密集型任務,就需要盡量壓榨CPU,參考值可以設置為NCPU+ 1。如果是IO密集型任務,參考值可以設置為2*NCPU。

 1 package com.bie.concurrency.example.threadpool;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 import lombok.extern.slf4j.Slf4j;
 7 
 8 /**
 9  * 
10  *
11  * @Title: ThreadPoolExample1.java
12  * @Package com.bie.concurrency.example.threadpool
13  * @Description: TODO
14  * @author biehl
15  * @date 2020年1月20日
16  * @version V1.0
17  * 
18  *          線程池的使用
19  */
20 @Slf4j
21 public class ThreadPoolExample1 {
22 
23     public static void main(String[] args) {
24 
25         // Executors.newCachedThreadPool,命名了一個新的線程池,創建了一個可以緩存的線程池,
26         // 如果線程池的長度超過了處理的需要,可以靈活回收空閑線程,如果沒有可以回收的,就新建線程。
27         // ExecutorService executorService = Executors.newCachedThreadPool();
28 
29         // Executors.newFixedThreadPool,創建一個定長的線程池,可以控制線程的最大並發數目,超出的線程會在隊列里面等待。
30         // ExecutorService executorService = Executors.newFixedThreadPool(3);
31 
32         // Executors.newSingleThreadExecutor,創建的是一個單線程化的線程池,
33         // 只會用唯一的一個工作線程執行任務,保證所有任務按照指定順序執行,可以指定先入先出,優先級等等進行執行。
34         ExecutorService executorService = Executors.newSingleThreadExecutor();
35 
36         for (int i = 0; i < 10; i++) {
37             final int index = i;
38 
39             executorService.execute(new Runnable() {
40 
41                 @Override
42                 public void run() {
43                     log.info("搞事情序號: {} ", index);
44                 }
45             });
46         }
47         executorService.shutdown();
48     }
49 
50 }

 

 1 package com.bie.concurrency.example.threadpool;
 2 
 3 import java.util.Date;
 4 import java.util.Timer;
 5 import java.util.TimerTask;
 6 import java.util.concurrent.Executors;
 7 import java.util.concurrent.ScheduledExecutorService;
 8 
 9 import lombok.extern.slf4j.Slf4j;
10 
11 /**
12  * 
13  *
14  * @Title: ThreadPoolExample1.java
15  * @Package com.bie.concurrency.example.threadpool
16  * @Description: TODO
17  * @author biehl
18  * @date 2020年1月20日
19  * @version V1.0
20  * 
21  *          線程池的使用
22  */
23 @Slf4j
24 public class ThreadPoolExample2 {
25 
26     public static void main(String[] args) {
27 
28         ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
29 
30         // long initialDelay = 1;// 線程池調度放行以后,延遲一秒
31         // long period = 3;// 間隔三秒執行
32         // TimeUnit unit = TimeUnit.SECONDS;// 秒的單位
33         // executorService.scheduleAtFixedRate(new Runnable() {
34         //
35         // @Override
36         // public void run() {
37         // log.warn("schedule run......");
38         //
39         // }
40         // }, initialDelay, period, unit);
41 
42         // executorService.shutdown();// 線程關閉
43 
44         // 定時器
45         Timer timer = new Timer();
46         Date time = new Date(); // 初始時間
47         long period2 = 5 * 1000;// 間隔5秒
48         timer.schedule(new TimerTask() {
49 
50             @Override
51             public void run() {
52                 log.warn("timer run");
53             }
54         }, time, period2);
55 
56     }
57 
58 }

 

 

作者:別先生

博客園:https://www.cnblogs.com/biehongli/

如果您想及時得到個人撰寫文章以及著作的消息推送,可以掃描上方二維碼,關注個人公眾號哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM