多線程高併發這一篇就夠了不用再去別家了

  高伸縮性的併發編程是一種藝術,是成爲高級程序員的必備知識點之一,最近總結了一下相關方面的知識。
  借鑑過得博客有的我也不知道原文作業是誰

https://blog.csdn.net/qq_34337272/article/details/81072874
https://www.cnblogs.com/dolphin0520/p/3932921.html

一、線程
什麼是線程?
線程是進程中單一順序的控制流。
什麼是進程?
進程是程序運行的過程。
1、進程與線程的關係:
1)線程是進程的最小執行單元;
2)一個進程中至少存在一個線程;
3)線程決定進程的生命週期 —— 一個線程啓動,進程就開始執行;所有線程執行結束,這個進程執行結束;
4)多個線程共享一個進程的內存空間、一組系統資源。
2、進程與線程的區別:
1)每個進程都有自己的獨立的內存空間、一組系統資源,而線程共享所屬進程的內存空間、一組系統資源;
進程是獨立的,同一個進程的線程是有聯繫的。
2)進程之間通信開銷較大,線程之間通信開銷較小。
3、多線程
一個進程中可以同時存在多個線程;
同一個進程中的多個線程之間可以併發執行;
多線程的執行方式:搶佔式;
搶佔CPU資源,計算機由CPU執行程序,只有擁有CPU資源的程序,纔會被執行。
CUP資源:CUP的控制權,這個控制權具有時間性,時間長度是隨機的;這段時間非常短,所以將其稱爲時間片。
時間片:線程搶佔到一段"CPU控制權"的時間。
4、多線程內存如何分配
棧 獨立
堆 共享
方法區 共享
程序計數器 獨立
記錄程序執行到位置。
5、自定義線程的兩種方式:
1)繼承Thread類,重寫run()方法
創建線程對象:
new 子類的構造方法(參數列表);
2)實現Runnable接口,重寫run()方法
創建線程對象:
new Thread(new 實現類構造方法(參數列表));
注意:Runnable接口只有一個run()方法。
6、Thread類常用方法:
void start()
//使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
static void yield()
//暫停當前正在執行的線程對象,並執行其他線程。
static void sleep(long millis)
//在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)。
void interrupt()
//中斷線程。
int getPriority()
//返回線程的優先級。
void setPriority(int newPriority)
//更改線程的優先級。 優先級的範圍:1…10 1最小,10最大,默認5。
boolean isAlive()
//測試線程是否處於活動狀態。 活的狀態:就緒、運行、阻塞
static int activeCount()
//返回當前線程的線程組中活動線程的數目。
void join()
//等待該線程終止。

7、**線程五大狀態**
   1)新建狀態
      使用new關鍵字創建線程對象,此時就是新建狀態。
   2)就緒狀態
      線程對象調用start方法,進入就緒狀態,處於就緒狀態的線程並不一定馬上就會執行 run 方法,還需要等待CPU的調度。
   3)運行狀態
      線程獲得CUP資源,執行run()方法,進入運行狀態。
   4)阻塞狀態
      調用sleep()方法、等待I/O資源、、等待同步鎖、調用wait()等待被喚醒等,進入阻塞狀態。
      注意:進入阻塞狀態的線程,會立刻讓出CPU資源(控制權)。
   5)結束狀態
      run()執行完畢,進入結束狀態;  正常結束。
      調用stop()、destroy()方法,進入結束狀態;  異常結束。
      使用interrupt()方法設置中斷標識,當線程發生阻塞時,進入結束狀態;  異常結束。
 8、線程的協作
    1)join();   當前線程等待另一個線程執行結束。
 9、線程的結束
    正常結束:run方法執行完畢。
    異常結束:使用stop()、destroy()方法強制結束線程,已過時,不推薦使用。
              使用interrupt()方法中斷線程,來實現強制結束線程。
    注意:interrupt()方法的本意並不是強制結束線程,而是設置中斷標識,設置了中斷標識的線程,在發生阻塞時,
          拋出異常,進入就緒狀態。
          要實現強制結束線程,run方法中的實現(代碼)全寫在try{}中,若拋出異常,try{}中的剩餘代碼不再執行,
          異常處理後,run()方法就執行結束;線程進入結束狀態。

二、高併發
什麼叫高併發?
高併發(High Concurrency)是一種系統運行過程中遇到的一種“短時間內遇到大量操作請求”的情況,主要發生在web系統集中大量訪問收到大量請求(例如:12306的搶票情況;天貓雙十一活動)。該情況的發生會導致系統在這段時間內執行大量操作,例如對資源的請求、數據庫的操作等。
高併發相關常用的一些指標有:響應時間、吞吐量、每秒查詢率QPS、併發用戶數
1、響應時間(Response Time)
響應時間:系統對請求做出響應的時間。例如系統處理一個HTTP請求需要200ms,這個200ms就是系統的響應時間
2、吞吐量(Throughput)
吞吐量:單位時間內處理的請求數量。
3、每秒查詢率QPS(Query Per Second)
QPS:每秒響應請求數。在互聯網領域,這個指標和吞吐量區分的沒有這麼明顯。
4、併發用戶數
併發用戶數:同時承載正常使用系統功能的用戶數量。例如一個即時通訊系統,同時在線量一定程度上代表了系統的併發用戶數。

三、高併發和多線程的關係和區別
“高併發和多線程”總是被一起提起,給人感覺兩者好像相等,實則 高併發 ≠ 多線程
多線程是Java的特性,因爲現在cpu都是多核多線程的,可以同時執行幾個任務,爲了提高jvm的執行效率,Java提供了這種多線程的機制,以增強數據處理效率。多線程對應的是cpu,高併發對應的是訪問請求
在這裏插入圖片描述在這裏插入圖片描述
總之:多線程即可以這麼理解:多線程是處理高併發的一種編程方法,即併發需要用多線程實現。
併發編程中,我們通常會遇到以下三個問題:原子性問題,可見性問題,有序性問題
1、原子性:即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
2、可見性:可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值
3、有序性:即程序執行的順序按照代碼的先後順序執行,一般來說,處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2之前執行。指令重排序不會影響單個線程的執行,但是會影響到線程併發執行的正確性。
所以:要想併發程序正確地執行,必須要保證原子性、可見性以及有序性。只要有一個沒有被保證,就有可能會導致程序運行不正確。

volatile 的作用是作爲指令關鍵字,確保本條指令不會因編譯器的優化而省略
一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:
  1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
  2)禁止進行指令重排序。
需要保證操作是原子性操作,才能保證使用volatile關鍵字的程序在併發時能夠正確執行

懶加載(lazy loading)—即爲延遲加載,顧名思義在需要的時候才加載,這樣做效率會比較低,但是佔用內存低,iOS設備內存資源有限,如果程序啓動使用一次性加載的方式可能會耗盡內存,這時可以使用懶加載,先判斷是否有,沒有再去創建
懶加載的好處:不必將創建對象的代碼全部寫在viewDidLoad方法中,代碼的可讀性更強代碼之間的獨立性強,低耦合,節省了內存資源
線程安全就是同步,
線程不安全就是異步

下面是關於鎖的總結很詳細
樂觀鎖和悲觀鎖:
樂觀鎖和悲觀鎖都是一種思想,並不是真實存在於數據庫中的一種機制。

悲觀鎖:
當認爲數據被併發修改的機率比較大,需要在修改之前藉助於數據庫鎖機制,先對數據進行加鎖的思想被稱爲悲觀鎖,又稱PCC(Pessimistic Concurrency Control)。在效率方面,處理鎖的操作會產生了額外的開銷,而且增加了死鎖的機會。當一個線程在處理某行數據的時候,其它線程只能等待。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖:
樂觀鎖思想認爲,數據一般是不會造成衝突的。只有在提交數據的時候,纔會對數據的衝突進行檢測。當發現衝突的時候,返回錯誤的信息,讓用戶決定如何去做,樂觀鎖不會使用數據庫提供的鎖機制,一般的實現方式就是記錄數據版本。
在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
樂觀鎖的實現不需要藉助於數據庫鎖機制,只要就是兩個步驟:衝突檢測和數據更新,其中一種典型的是實現方法就是CAS(Compare and Swap),但可能會導致ABA問題,解決ABA問題的一個方法是通過一個順序遞增的version字段,版本號機制。
CAS:CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試,自旋CAS(也就是不成功就一直循環執行直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。
ABA 問題
如果一個變量V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因爲在這段時間它的值可能被改爲其他值,然後又改回A,那CAS操作就會誤認爲它從來沒有被修改過。這個問題被稱爲CAS操作的 "ABA"問題。
//查詢出商品庫存信息,quantity = 3,version=1
select version from items where id=1
//修改商品庫存爲2
update items set quantity=2,version=2 where id=1 and version=1;
##樂觀鎖和悲觀鎖對比
樂觀鎖並不是真正的加鎖,優點是效率高,缺點是更新失敗的概率比較高;悲觀鎖依賴於數據庫鎖機制,更新失敗的概率比較低,但是效率也低。
兩種鎖的使用場景
從上面對兩種鎖的介紹,我們知道兩種鎖各有優缺點,不可認爲一種好於另一種,像樂觀鎖適用於寫比較少的情況下(多讀場景),即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果是多寫的情況,一般會經常產生衝突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了性能,所以一般多寫的場景下用悲觀鎖就比較合適。
CAS與synchronized的使用情景
簡單的來說CAS適用於寫比較少的情況下(多讀場景,衝突一般較少),synchronized適用於寫比較多的情況下(多寫場景,衝突一般較多)

synchronized關鍵字
Java併發編程這個領域中synchronized關鍵字一直都是元老級的角色,很久之前很多人都會稱它爲 “重量級鎖” 。但是,在JavaSE 1.6之後進行了主要包括爲了減少獲得鎖和釋放鎖帶來的性能消耗而引入的 偏向鎖 和 輕量級鎖 以及其它各種優化之後變得在某些情況下並不是那麼重了。synchronized的底層實現主要依靠 Lock-Free 的隊列,基本思路是 自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程衝突較少的情況下,可以獲得和CAS類似的性能;而線程衝突嚴重的情況下,性能遠高於CAS。現在的 synchronized 鎖效率也優化得很不錯了。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。
synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:
1)、修飾一個代碼塊:被修飾的代碼塊稱爲同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象,修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖
2)、修飾一個方法:被修飾的方法稱爲同步方法,其作用的範圍是整個方法,作用的對象是調用這個方法的對象
3)、修改一個靜態的方法:其作用的範圍是整個靜態方法,作用的對象是這個類的所有對象
4)、修改一個類:其作用的範圍是synchronized後面括號括起來的部分,作用主的對象是這個類的所有對象。

synchronized 關鍵字加到 static 靜態方法和 synchronized(class) 代碼塊上都是對當前 Class 類上鎖;
synchronized 關鍵字加到實例方法上是給對象實例上鎖;
儘量不要使用 synchronized(String s),因爲 JVM 中,字符串常量池具有緩存功能。
線程安全就是同步,
線程不安全就是異步
鎖主要存在四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。注意鎖可以升級不可降級,這種策略是爲了提高獲得鎖和釋放鎖的效率。
所有用戶程序都是運行在用戶態的, 但是有時候程序確實需要做一些內核態的事情, 例如從硬盤讀取數據, 或者從鍵盤獲取輸入等. 而唯一可以做這些事情的就是操作系統,掛起線程和恢復線程的操作都需要轉入內核態中完成。
JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。,互斥同步對性能最大的影響就是阻塞的實現,因爲掛起線程/恢復線程的操作都需要轉入內核態中完成(用戶態轉換到內核態會耗費時間)。
偏向鎖:是爲了沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗,偏向鎖在無競爭的情況下會把整個同步都消除掉。會偏向於第一個獲得它的線程,如果在接下來的執行中,該鎖沒有被其他線程獲取,那麼持有偏向鎖的線程就不需要進行同步,對於鎖競爭比較激烈的場合,偏向鎖就失效了,因爲這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失。
輕量級鎖:若偏向鎖失敗,虛擬機並不會立即升級爲重量級鎖,而是膨脹爲輕量級鎖,本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗,因爲使用輕量級鎖時,不需要申請互斥量,輕量級鎖的加鎖和解鎖都用到了CAS操作,輕量級鎖能夠提升程序同步性能的依據是“對於絕大部分鎖,在整個同步週期內都是不存在競爭的”,這是一個經驗數據。如果沒有競爭,輕量級鎖使用 CAS 操作避免了使用互斥操作的開銷。但如果存在鎖競爭,除了互斥量開銷外,還會額外發生CAS操作,因此在有鎖競爭的情況下,輕量級鎖比傳統的重量級鎖更慢!如果鎖競爭激烈,那麼輕量級將很快膨脹爲重量級鎖。
自旋鎖:輕量級鎖失敗後,虛擬機爲了避免線程真實地在操作系統層面掛起,還會進行一項稱爲自旋鎖的優化手段,爲了讓一個線程等待,我們只需要讓線程執行一個忙循環(自旋),這項技術就叫做自旋。需要注意的是:自旋等待不能完全替代阻塞,因爲它還是要佔用處理器時間。如果鎖被佔用的時間短,那麼效果當然就很好了!反之,相反!自旋等待的時間必須要有限度。如果自旋超過了限定次數任然沒有獲得鎖,就應該掛起線程。自旋次數的默認值是10次
自適應自旋:在 JDK1.6 中引入了自適應的自旋鎖。自適應的自旋鎖帶來的改進就是:自旋的時間不在固定了,而是和前一次同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定,虛擬機變得越來越“聰明”了,有了自適應自旋,隨着程序運行和性能監控信息的不斷完善,虛擬機對程序鎖的狀況預測會越來越準確。
鎖消除:指虛擬機即時編譯器在運行時,對一些代碼上要求同步但是被檢測到不可能存在共享數據競爭的鎖進行消除,鎖消除的主要判斷依據來源於逃逸分析的數據支持。
鎖粗化:如果一些列的連續操作都對同一個對象反覆加鎖和解鎖,甚加鎖操作是出現在循環體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導致不必要的性能損耗,會把多次對同一個個對象的加鎖操作簡化,只進行一次操作。
重量級鎖:ReenTrantLock 和 synchronized
兩者都是可重入鎖,自己可以再次獲取自己的內部鎖。
synchronized 依賴於 JVM 而 ReenTrantLock 依賴於 API,synchronized 是依賴於 JVM 實現的,前面我們也講到了 虛擬機團隊在 JDK1.6 爲 synchronized 關鍵字進行了很多優化,但是這些優化都是在虛擬機層面實現的,並沒有直接暴露給我們。ReenTrantLock 是 JDK 層面實現的(也就是 API 層面,需要 lock() 和 unlock 方法配合 try/finally 語句塊來完成),所以我們可以通過查看它的源代碼,來看它是如何實現的。
性能已不是選擇標準
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具體表示爲:synchronized 關鍵字吞吐量歲線程數的增加,下降得非常嚴重。而ReenTrantLock 基本保持一個比較穩定的水平。我覺得這也側面反映了, synchronized 關鍵字還有非常大的優化餘地。後續的技術發展也證明了這一點,我們上面也講了在 JDK1.6 之後 JVM 團隊對 synchronized 關鍵字做了很多優化。JDK1.6 之後,synchronized 和 ReenTrantLock 的性能基本是持平了。所以網上那些說因爲性能才選擇 ReenTrantLock 的文章都是錯的!JDK1.6之後,性能已經不是選擇synchronized和ReenTrantLock的影響因素了!而且虛擬機在未來的性能改進中會更偏向於原生的synchronized,所以還是提倡在synchronized能滿足你的需求的情況下,優先考慮使用synchronized關鍵字來進行同步!優化後的synchronized和ReenTrantLock一樣,在很多地方都是用到了CAS操作。

線程間的協作機制
wait 類方法用於阻塞當前線程,將當前線程掛載進 Wait Set 隊列,notify 類方法用於釋放一個或多個處於等待隊列中的線程。
所以,這兩個方法主要是操作對象的等待隊列,也即是將那些獲得鎖但是運行期間缺乏繼續執行的條件的線程阻塞和釋放的操作,但是有一個前提大家需要注意,wait 和 notify 操作的是對象內置鎖的等待隊列,也就是說,必須在獲得對象內置鎖的前提下才能阻塞和釋放等待隊列上的線程。簡單來說,這兩個方法的只能在 synchronized 修飾的代碼塊內部進行調用,雖然阻塞隊列和等待隊列上的線程都不能得到 CPU 正常執行指令,但是它們卻屬於兩種不同的狀態,阻塞隊列上的線程在得知鎖已經釋放後將公平競爭鎖資源,而等待隊列上的線程則必須有其他線程通過調用 notify 方法通知並移出等待隊列進入阻塞隊列,重新競爭鎖資源。join 方法用於實現兩個線程之間相互等待的一個操作。

線程池
如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因爲頻繁創建線程和銷燬線程需要時間。
那麼有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務
一.Java中的ThreadPoolExecutor類
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,ThreadPoolExecutor繼承了AbstractExecutorService類,並提供了四個構造器,以下是構造器中幾個參數:
int corePoolSize:默認情況下,在創建了線程池後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中
int maximumPoolSize:它表示在線程池中最多能創建多少個線程;
long keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;
TimeUnit unit:參數keepAliveTime的時間單位
BlockingQueue workQueue:一個阻塞隊列,用來存儲等待執行的任務
ThreadFactory threadFactory:線程工廠,主要用來創建線程;
RejectedExecutionHandler handler:表示當拒絕處理任務時的策略,有以下四種取值:
源代碼解析
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關係了。
  Executor是一個頂層接口,在它裏面只聲明瞭一個方法execute(Runnable),返回值爲void,參數爲Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;
  然後ExecutorService接口繼承了Executor接口,並聲明瞭一些方法:submit、invokeAll、invokeAny以及shutDown等;
  抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;
  然後ThreadPoolExecutor繼承了類AbstractExecutorService。
在ThreadPoolExecutor類中有幾個非常重要的方法:
execute():這個方法可以向線程池提交一個任務,交由線程池去執行。
submit():這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果
shutdown():用來關閉線程池
shutdownNow():用來關閉線程池

二.深入剖析線程池實現原理
線程池的具體實現原理:
如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;
如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
線程池中的線程初始化:
 默認情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程。
  在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:
prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程
任務緩存隊列及排隊策略:
在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。
  workQueue的類型爲BlockingQueue,通常可以取下面三種類型:
  1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;
  2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;
  3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。
任務拒絕策略:
當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略,通常有以下四種策略:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
線程池的關閉:
ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務
線程池容量的動態調整
ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:設置核心池大小
setMaximumPoolSize:設置線程池最大能創建的線程數目大小
  當上述參數從小變大時,ThreadPoolExecutor進行線程賦值,還可能立即創建新的線程來執行任務。
三.使用示例
四.如何合理配置線程池的大小
  線程中的任務最終是交給CPU的線程去處理的,而CPU可同時處理線程數量大部分是CPU核數的兩倍,運行環境中CPU
的核數我們可以通過Runtime.getRuntime().availableProcessors()這個方法而獲取。理論上來說核心池線程數量應該爲
Runtime.getRuntime().availableProcessors()*2,那麼結果是否符合我們的預期呢,可以來測試一下(本次測試測試的
是I/O密集型任務,事實上大部分的任務都是I/O密集型的,即大部分任務消耗集中在的輸入輸出。而CPU密集型任務主
要消耗CPU資源進行計算,當任務爲CPU密集型時,核心池線程數設置爲CPU核數+1即可)

裏面的東西很多都是我參考自《深入理解Java虛擬機:JVM高級特性與最佳實踐》第二版的13章第三節鎖優化裏面的內容。

下面是一些具體的實例,理論知識學了,需要來點小的測試加深印象
雙重效驗鎖機制

package com.zyu.springandmybatis.Thread;

//雙重效驗鎖(double-checked locking) 這種方式採用雙鎖機制,安全且在多線程情況下能保持高性能。
public class Singleton {
    // DCL 方式的單例需確保使用 volatile
    private volatile static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        // 先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼
        if (singleton == null) {
            ///類對象加鎖
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}


//餓漢模式,很餓很着急,所以類加載時即創建實例對象
class Singleton1 {
    private static Singleton1 singleton = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        return singleton;
    }
}

//飽漢模式,很飽不着急,延遲加載,啥時候用啥時候創建實例,存在線程安全問題
class Singleton2 {
    private static Singleton2 singleton;

    private Singleton2() {
    }

    public static synchronized Singleton2 getInstance() {
        if (singleton == null)
            singleton = new Singleton2();
        return singleton;
    }
}

測試使用中斷標識結束線程

package com.zyu.springandmybatis.Thread;


/**
 * 輸出字符串線程
 * @author zyu
 */
class PrintStringThread extends Thread {
	
	@Override
	public void run() {
		try {
			for(int i=0; i<50; i++) {
				System.out.println("test!"+i);
				sleep(200);
			}
		} catch(InterruptedException e) {
			//e.printStackTrace();
		}
	}
	
}

/**
 * 使用中斷標識結束線程
 */
public class TestInterrupt {

	public static void main(String[] args) {
        Thread t = new PrintStringThread();
        //t.interrupt();
        t.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t.interrupt();
	}

}

測試 join

package com.zyu.springandmybatis.Thread;

/**
 */
class PrintCharThread extends Thread {
	
	private char c;
	private int times;
	
	public PrintCharThread(char c, int times) {
		this.c = c;
		this.times = times;
	}
	
	@Override
	public void run() {
		for(int i=0; i<times; i++) {
			if(i!=0&&i%10==0) {
				System.out.println();
			}
			System.out.print(c);
			System.out.print(' ');
		}
	}
	
}


public class TestPrintCharThread {

	public static void main(String[] args) throws InterruptedException {
		/*//創建線程組
		ThreadGroup tg = new ThreadGroup("線程組");
		//循環創建10個線程
		for(int i=0; i<10; i++) {
			//創建線程,並添加到線程組中
			Thread t = new Thread(tg, new PrintCharThread((char)('A'+i), 100));
			//啓動線程
			t.start();
		}
		//循環判斷線程組中是否存在活動線程
		while(tg.activeCount()>0) {
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("\n程序執行完畢!");*/
		
		//創建線程對象
		Thread t1 = new PrintCharThread('渝', 100);
		Thread t2 = new PrintCharThread('川', 100);
		t1.start();
		t2.start();
	    //當前線程等待線程t1、t2執行結束
		t1.join();
		t2.join();
		System.out.println("\n程序執行完畢!");
	}

}

創建一個線程安全的線程

package com.zyu.springandmybatis.Thread;

public class TestThreadSafe {
    public static void main(String[] args) {
        Person p = new Person();
        Thread t1 = new Thread(p, "線程1");
        Thread t2 = new Thread(p, "線程2");
        Thread t3 = new Thread(p, "線程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Person implements Runnable {
    private int count = 50;

    //線程不安全
    public void run() {
        while (true) {
            if (count == 0)
                break;
// Thread.sleep(100)是爲了增加線程不安全的概率
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ".." + count--);
        }
    }
    //線程安全
//    public void run() {
//        while (true) {
//            synchronized (this) {
//                if (count == 0)
//                    break;
//// Thread.sleep(100)s是爲了增加線程不安全的概率
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread() + ".." + count--);
//            }
//        }
//    }
}

經典的消費者生產者模型測試

package com.zyu.springandmybatis.Thread.線程協作.生產者消費者模型;

import java.util.ArrayList;
import java.util.List;

public class Repository {
    private List<Integer> list = new ArrayList<>();
    private int limit = 10;  //設置倉庫容量上限

    public synchronized void addGoods(int count) throws InterruptedException {
        while (list.size() == limit) {
            //達到倉庫上限,不能繼續生產
            wait();
        }
        list.add(count);
        System.out.println("生產者生產產品:" + count);
        //通知所有的消費者
        notifyAll();
    }

    public synchronized void removeGoods() throws InterruptedException {
        while (list.size() <= 0) {
            //倉庫中沒有產品
            wait();
        }

        int res = list.get(0);
        list.remove(0);
        System.out.println("消費者消費產品:" + res);
        //通知所有的生產者
        notifyAll();
    }

    //測試生產者消費者模型
    public static void main(String[] args) throws InterruptedException {
        Repository repository = new Repository();
        Thread producer = new Producer(repository);
        Thread consumer = new Customer(repository);
        producer.start();
        consumer.start();
        producer.join();
        consumer.join();
        System.out.println("main thread finished..");
    }
}


//消費者
class Customer extends Thread {
    Repository repository = null;

    public Customer(Repository p) {
        this.repository = p;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep((long) (Math.random() * 500));
                repository.removeGoods();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生產者
class Producer extends Thread {
    Repository repository = null;

    public Producer(Repository p) {
        this.repository = p;
    }

    @Override
    public void run() {
        int count = 1;
        while (true) {
            try {
                Thread.sleep((long) (Math.random() * 500));
                repository.addGoods(count++);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


測試線程池的使用

package com.zyu.springandmybatis.Thread.線程池;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 *
 * 測試線程池的使用
 */
public class test1 {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));

        for(int i=0;i<15;i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+
                    executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}


class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("正在執行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"執行完畢");
    }
}

碼字不易,希望對你有所幫助。

發佈了27 篇原創文章 · 獲贊 24 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章