線程池核心概述

線程池核心概述

  • Executors工廠類使用?? ??? ??? ??? ??? ??? ?
  • Executors工廠類底層源碼分析詳解?? ??? ??? ??? ?
  • ThreadPoolExecutor自定義線程池?? ??? ??? ??? ?
  • ThreadPoolExecutor拒絕策略詳解?? ??? ??? ??? ?
  • 計算機密集型與IO密集型詳解?? ??? ??? ?
  • 如何正確的使用線程池

線程池初步

  • 線程池,一般高并發其實是一個非常抽象的概念,要實現高并發其實不僅僅是一個JAVA 線程集合類、或者JAVA基礎層面就能搞定的事情,在互聯網大廠中,高并發其實涉及方方面面,從前端到后端,到支持高并發的中間組件(redis、zookeper等),最后到數據存儲,持久化層面等等,都需要對高并發做一些考量和設計
  • 管理控制:首先,從管理角度就是為了更好的控制線程,使用線程池來幫助我們去管理線程,使得我們對線程的生命周期、初始化、運行狀態、銷毀等各個環節有一個把控
  • 系統資源:另外一點,從系統資源的角度考慮,線程池可以控制線程的數量,根據任務的多少去對線程池中的線程個數進行添加或者減少,可以回收空閑狀態的線程,減少線程的頻繁初始化和銷毀,避免不必要的系統開銷,節省系統資源,保障穩定性
  • 應用性能:從性能的角度去考慮,線程池可以配合高并發容器的設置,對任務和工作項進行緩存,異步的多線程的去處理任務,從而提高應用服務的吞吐率、消費性能,也從而提高單個線程的利用率
  • 兜底策略:從健壯性的角度去分析,線程池提供了很多拒絕策略,我們在任務過多或者處理不過來的時候,可以進行有效的拒絕策略、降級方案,以補償的形式進行處理任務,避免因為線程池的問題對系統產生較為嚴重的影響

Executors

  • JDK提供了一套線程框架Executors,存儲在java.util.concurrent包中,是JDK并發包的核心
  • Executors:線程工廠的角色,通過Executors可以創建特定功能的線程池

Executors創建線程池的方法

  • newFixedThreadPool()方法:該方法返回一個固定數量的線程池,該方法的線程數始終不變,當有一個任務提交時,若線程池中空閑,則立即執行,若沒有,則會被暫緩在一個任務隊列中等待有空閑的線程去執行
  • newSingleThreadPool ()方法:創建一個線程的線程池,若空閑則執行,若沒有空閑線程則暫緩在任務列隊中
  • newCachedThreadPool()方法:返回一個可根據實際情況調整線程個數的線程池,不限制最大線程數量,若有任務,則創建線程,若無任務則不創建線程。如果沒有任務則線程在60s后自動回收(空閑時間60s)
  • newScheduledThreadPool()方法:該方法返回一個SchededExecutorService對象,但該線程池可以指定線程的數量

自定義線程池ThreadPoolExecutor

  • 自定義線程池:若Executors工廠無法滿足我們的需求,可以自己創建自定義線程池,其實Executors工廠類里面的創建線程方法其內部實現均是用了ThreadPoolExecutor這個類,這個類可以自定義線程。構造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
  • 使用有界隊列:在使用有界隊列時,若有新的任務需要執行,如果線程池實際線程數小于corePoolSize,則優先創建線程,若大于corePoolSize,則會將任務加入隊列,若隊列已滿,則在總線程數不大于maximumPoolSize的前提下,創建新的線程,若線程數大于maximumPoolSize,則執行拒絕策略。或其他自定義方式
  • 使用無界隊列:在使用無界隊列時:LinkedBlockingQueue。與有界隊列相比,除非系統資源耗盡,否則無界的任務隊列不存在任務入隊失敗的情況。當有新任務到來,系統的線程數小于corePoolSize時,則新建線程執行任務。當達到corePoolSize后,就不會繼續增加。若后續仍有新的任務加入,而有沒有空閑的線程資源,則任務直接進入隊列等待。若任務創建和處理的速度差異很大,無界隊列會保持快速增長,直到耗盡系統內存

線程池的拒絕策略

  • AbortPolicy:直接拋出異常阻止系統正常工作
  • CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務
  • DiscardOldestPolicy:丟棄最老的一個請求,嘗試再次提交當前任務
  • DiscardPolicy:丟棄無法處理的任務,不給予任何處理
  • 如果需要自定義拒絕策略可以實現RejectedExecutionHandler接口
    //RejectedExecutionHandler接口
    public class MyRejected implements RejectedExecutionHandler{public MyRejected(){}@Overridepublic void rejectExecution(Runnable r,ThreadPoolExecutor executor){}
    }

如何使用好線程池

  • 線程個數大小的設置
  • 線程池相關參數配置
  • 利用Hook嵌入你的行為
  • 線程池的關閉

線程池大小設置(計算密集型/IO密集型)

  1. 計算密集型: 顧名思義就是應用需要非常多的CPU計算資源,在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費服務器配置,如果在非常好的服務器配置上還運行著單線程程序那將是多么重大的浪費。對于計算密集型的應用,完全是靠CPU的核數來工作,所以為了讓它的優勢完全發揮出來,避免過多的線程上下文切換。比較理想方案是: 線程數 = CPU核數+1,也可以設置成CPU核數*2,但還要看JDK的版本以及CPU配置(服務器的CPU有超線程)
  2. IO密集型: 就很好理解了,我們現在做的開發大部分都是WEB應用,涉及到大量的網絡傳輸,不僅如此,與數據庫,與緩存間的交互也涉及到IO,一旦發生IO,線程就會處于等待狀態,當IO結束,數據準備好后,線程才會繼續執行。因此從這里可以發現,對于IO密集型的應用,我們可以多設置一些線程池中線程的數量,這樣就能讓在等待IO的這段時間內,線程可以去做其它事,提高并發處理效率。那么這個線程池的數據量是不是可以隨便設置呢?當然不是的,請一定要記得,線程上下文切換是有代價的。目前總結了一套公式,對于IO密集型應用: 線程數 = CPU核心數/(1-阻塞系數) 這個阻塞系數一般為0.8~0.9之間,也可以取0.8或者0.9。 套用公式,對于雙核CPU來說,它比較理想的線程數就是20,當然這都不是絕對的,需要根據實際情況以及實際業務來調整:final int poolSize = (int)(cpuCore/(1-0.9))

線程池相關參數配置注意事項

  1. 避免線上操作數據庫,查詢、修改都很麻煩
  2. 使用線程池的時候都不要選擇沒有上限限制的配置項
  3. 不要使用沒有上限的線程池和設置無界隊列
  4. newCachedThreadPool的設置與無界隊列的設置因為某些不可預期的情況,線程池會出現系統異常,導致線程暴增的情況或者任務隊列不斷膨脹,內存耗盡導致系統崩潰和異常。 我們推薦使用自定義線程池來避免該問題,這也是在使用線程池規范的首要原則
  5. 合理設置線程數量、和線程空閑回收時間,根據具體的任務執行周期和時間去設定,避免頻繁的回收和創建,雖然我們使用線程池的目的是為了提升系統性能和吞吐量,但是也要考慮下系統的穩定性,不然出現不可預期問題會很麻煩
  6. 根據實際場景,選擇適用于自己的拒絕策略。進行補償,不要亂用JDK支持的自動補償機制!盡量采用自定義的拒絕策略去進行兜底

利用Hook嵌入你的行為

  1. 利用Hook,留下線程池執行軌跡
  2. 例如ThreadPoolExecutor提供了protected類型可以被覆蓋的鉤子方法,允許用戶在任務執行之前會執行之后做一些事情。我們可以通過它來實現比如初始化ThreadLocal、收集統計信息、如記錄日志等操作。這類Hook如beforeExecute和afterExecute。另外還有一個Hook可以用來在任務被執行完的時候讓用戶插入邏輯,如rerminated
  3. 如果hook方法執行失敗,則內部的工作線程的執行將會失敗或被中斷

???????關閉線程池

  1. 內容當線程池不在被引用并且工作線程數為0的時候,線程池將被終止。我們也可以調用shutdown來手動終止線程池。如果我們忘記調用shutdown,為了讓線程資源被釋放,我們還可以使用keepAliveTime和allowCoreThreadTimeOut來達到目的
  2. 當然,穩妥的方式是使用虛擬機Runtime.getRuntime().addShutdownHook方法,手工去調用線程池的關閉方法

相關代碼

package com.bfxy.thread.core.pool;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class UseThreadPoolExecutor {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(1,	// corePoolSize: 核心線程數,線程池初始化的時候就會被創建3,	// maximumPoolSize: 線程池的最大上限	//在使用無界隊列的時候, 此參數 不起作用60,	//線程的存活時間TimeUnit.SECONDS,//workQueue:BlockingQueue接口下面的實現類//new ArrayBlockingQueue<>(2),	//使用有界隊列: ArrayBlockingQueuenew LinkedBlockingQueue<>(),	//使用無界隊列: LinkedBlockingQueuenew ThreadFactory() {	//threadFactory 線程工廠, 用于獲取一個新的線程, 然后把該線程 投遞到我們的線程池中去@Overridepublic Thread newThread(Runnable r) {Thread th = new Thread(r, "order-thread");if(th.getPriority() != Thread.NORM_PRIORITY) {th.setPriority(Thread.NORM_PRIORITY);}if(th.isDaemon()) {th.setDaemon(false);}return th;}},	//使用無界隊列時, 拒絕策略不起到作用new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.err.println("當前的任務已經被拒絕: " + r.toString());}});Task t1 = new Task(1);Task t2 = new Task(2);Task t3 = new Task(3);Task t4 = new Task(4);Task t5 = new Task(5);Task t6 = new Task(6);/**//線程池提交任務的方法:pool.execute(t1);  		//execute: 如果你的任務沒有返回值, 則使用該方法提交任務pool.submit(t1);		//submit: 如果你的任務有返回值, 則使用該方法提交任務, 返回一個Future對象(Future模式)*//*** * 在使用有界隊列時:* 1 若有新的任務需要執行,如果線程池實際線程數小于corePoolSize,則優先創建線程* 2 若大于corePoolSize,則會將任務加入隊列* 3 若隊列已滿,則在總線程數不大于maximumPoolSize的前提下,創建新的線程* 4 若線程數大于maximumPoolSize,則執行拒絕策略。*/// 1 若有新的任務需要執行,如果線程池實際線程數小于corePoolSize,則優先創建線程pool.execute(t1);	//core size = 1  t1任務會被核心線程執行// 2 若大于corePoolSize,則會將任務加入隊列pool.execute(t2);	// 有界隊列容量為: 2pool.execute(t3);// 3 若隊列已滿,則在總線程數不大于maximumPoolSize的前提下,創建新的線程, 并執行該任務pool.execute(t4);	// 線程池中的總線程數 2  , maximumPoolSize = 3 pool.execute(t5);	// 線程池中的總線程數 3  , maximumPoolSize = 3 // 4 若線程數大于maximumPoolSize,則執行拒絕策略。pool.execute(t6);pool.shutdown();}
}

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/447341.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/447341.shtml
英文地址,請注明出處:http://en.pswp.cn/news/447341.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網站盈利的10種方式

如果你有自己的網站&#xff0c;而且已經有了不少的流量&#xff0c;你肯定會開始考慮如何通過這個網站來掙一些錢。 在這篇文章中&#xff0c;我會向大家介紹網站最常見的10種盈利方式。 1.按點擊付費廣告 在網站上展示一個按點擊付費的廣告橫幅是最簡單的盈利方式&#xff…

程序員如何創業?

摘要&#xff1a;工作機會減少&#xff0c;讀大學也不是保障。大公司亦不再是構筑職業生涯的安全港灣。透過媒體的鏡頭&#xff0c;創業似乎成了沙漠中唯一的綠洲。然而關于創業&#xff0c;或許少有人給你建議&#xff0c;這里所列出的一些因素都是你可以考慮的。 如果你的年…

Redis數據的類型

Redis一共分為五種基本數據類型&#xff1a;String、Hash、List、Set、Zset. string 內部編碼有三種&#xff0c;raw&#xff0c;embstr&#xff0c;int String 是二進制的。可以存儲序列化對象&#xff0c;圖片&#xff0c;字符串&#xff0c;數值等 set和get方法 &#x…

Redis高級命令與特性以及單點模式的介紹

高級命令 keys * 返回滿足條件的所有key&#xff0c;可以模糊匹配exists 是否存在指定的keypersist 取消過期時間select 選擇數據庫 &#xff08;0-15&#xff0c;總共16個數據庫&#xff09;move key index 將當前數據庫的 key 移動到給定的數據庫 db 當中randomkey 隨機返回…

華為副總裁徐家駿離職:年薪千萬工作感悟十二條

從普通的公司職員&#xff0c;到年薪千萬的華為副總裁&#xff0c;再到離開華為轉戰百度&#xff0c;徐家駿的十年從業經歷和經驗可資借鑒&#xff0c;我們從中也可以一窺華為的運作過程。徐家駿是華為數據中心的頭&#xff0c;技術超級牛人&#xff0c;一級部門總監&#xff0…

Redis持久化之RDB和AOF

Redis持久化之RDB和AOF Redis 有兩種持久化方案&#xff0c;RDB &#xff08;Redis DataBase&#xff09;和 AOF &#xff08;Append Only File&#xff09;&#xff1b; RDB 詳解 RDB 是 Redis 默認的持久化方案。在指定的時間間隔內&#xff0c;執行指定次數的寫操作&#…

同為程序員 為什么我的工資最低

我看著工資單上每一個開發團隊成員的薪水&#xff0c;慢慢地我不能保持淡定了。 而當我看到我的工資排名是倒數的時候——靠近最后一名——我不由得倒抽一口冷氣。就像圣誕故事中的那個可愛的小男孩Ralphie &#xff0c;想買氣槍卻被忽悠會有危險一樣&#xff0c;我也不斷忽悠…

Docker安裝Redis以及配置Redis環境

1&#xff0c;下載Redis鏡像 首先拉取 Redis 鏡像, 這里我選擇的是 redis:alpine 輕量級鏡像版本 docker pull redis:alpine 下載完成后&#xff0c;通過 docker images 查看我們已經下載的鏡像&#xff0c;看看是否已經下載到本地 2&#xff0c;運行 Redis 容器 docker run …

.NET程序性能的基本要領

摘要&#xff1a;本文分享了性能優化的一些建議和思考&#xff0c;比如不要過早優化、好工具很重要、性能的關鍵&#xff0c;在于內存分配等。開發者不要盲目的沒有根據的優化&#xff0c;首先定位和查找到造成產生性能問題的原因點最重要。 【編者按】Bill Chiles&#xff08…

redis.conf配置文件詳解

基本配置 daemonize no #是否以后臺進程啟動databases 16 #創建database的數量(默認選中的是database 0)save 900 1 #刷新快照到硬盤中&#xff0c;必須滿足兩者要求才會觸發&#xff0c;即900秒之后至少1個關鍵字發生變化save 300 10 #必須是300秒之后至少10個關鍵字發生變…

什么原因成就了一位優秀的程序員?

這些年我曾和很多程序員一起工作&#xff0c;他們之中的一些人非常厲害&#xff0c;而另一些人顯得平庸。不久前因為和一些技術非常熟練的程序員工作感覺很愉快&#xff0c;我花了一些時間在考慮我佩服他們什么呢&#xff1f;什么原因讓優秀的程序員那么優秀&#xff0c;糟糕的…

Redis的哨兵模式Sentinel

sentinel功能 redis的sentinel系統用于管理多個redis服務器&#xff0c;該系統主要執行三個任務&#xff1a;監控、提醒、自動故障轉移。 1、監控&#xff08;Monitoring&#xff09;&#xff1a; Redis Sentinel實時監控主服務器和從服務器運行狀態&#xff0c;并且實現自動…

csdn 到底怎么了?不準轉載?

我轉載了20多文章&#xff0c;很多人閱讀過&#xff0c;但是今天看到閱讀量是0&#xff0c; csdn 到底怎么了&#xff1f; 對用戶這樣&#xff1f;請大家看看是不是這樣&#xff1f;

python3之后版本讀取網頁的內容

import urllib.request url "http://helloworldbook2.com/data/message.txt" #直接通過url來獲取網頁數據 print(第一種) response urllib.request.urlopen(url) code response.getcode() html response.read() mystr html.decode("utf8") response.c…

三十功名塵與土——資深程序員生涯自白

摘要&#xff1a;作者Codist&#xff08;網名&#xff09;在程序員崗位上工作了三十多年&#xff0c;在這期間他悟出了一些真理&#xff0c;比如&#xff0c;成功來自對失敗的總結學習&#xff1b;條條大路通羅馬&#xff0c;羅馬并不在乎你用什么方式到達。你在生活中積累了哪…

linux/unix核心設計思想

1&#xff09; 程序應該小而專一&#xff0c;程序應該盡量的小&#xff0c;且只專注于一件事上&#xff0c;不要開發那些看起來有用但是90%的情況都用不到的特性&#xff1b; 2&#xff09; 程序不只要考慮性能&#xff0c; 程序的可移植性更重要&#xff0c;shell和perl&…

操作系統環境變量

在 Java中&#xff0c;許多類都是 Iterable &#xff0c;主要包括所有的 Collection 類&#xff08;但不包括各種 Maps &#xff09;。 例如&#xff0c;下面的代碼可以顯示所有的操作系統環境變量&#xff1a; // collections/EnvironmentVariables.java // {VisuallyInspect…

用Unix的設計思想來應對多變的需求

摘要&#xff1a;無論是Unix設計&#xff0c;還是面向對象設計&#xff0c;還是別的什么如SOA&#xff0c;ECB&#xff0c;消息&#xff0c;事件&#xff0c;MVC&#xff0c;網絡七層模型&#xff0c;數據庫設計&#xff0c;等等&#xff0c;他們都在干三件事——解耦&#xff…

學習較底層編程:動手寫一個C語言編譯器

動手編寫一個編譯器&#xff0c;學習一下較為底層的編程方式&#xff0c;是一種學習計算機到底是如何工作的非常有效方法。 編譯器通常被看作是十分復雜的工程。事實上&#xff0c;編寫一個產品級的編譯器也確實是一個龐大的任務。但是寫一個小巧可用的編譯器卻不是這么困難。…

Arrays.deepToString() 方法同時適用于基元數組和對象數組

Arrays.deepToString() 方法同時適用于基元數組和對象數組&#xff1a; import java.util.*;public class MultiDimWrapperArray {public static void main(String[] args) {Integer[][] a1 { // Autoboxing{ 1, 2, 3, },{ 4, 5, 6, },};Double[][][] a2 { // Autoboxing{ {…