備戰2020面試題,Java面試題下(鎖、AQS、線程池)

點擊上方Java後端技術之路”,選擇“置頂或者星標

與你一起成長

十二、談談悲觀鎖、樂觀鎖、可重入鎖

樂觀鎖:每次獲取數據的時候,都不會擔心數據被修改,所以每次獲取數據的時候都不會進行加鎖,但是在更新數據的時候需要判斷該數據是否被別人修改過。如果數據被其他線程修改,則不進行數據更新,如果數據沒有被其他線程修改,則進行數據更新。由於數據沒有進行加鎖,期間該數據可以被其他線程進行讀寫操作。例如versioncas

悲觀鎖:例如for updatesync就是悲觀鎖。每次都會悲觀的認爲數據會被修改,所以每次讀數據都會加鎖。具體強烈的獨佔和排他性。給我了別人就不能用。

·  共享鎖又稱爲讀鎖,簡稱S鎖。顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是隻能讀不能修改。

·  排他鎖又稱爲寫鎖,簡稱X鎖。顧名思義,排他鎖就是不能與其他鎖並存,如果一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據行讀取和修改。

 

可重入鎖:

簡單說就是線程可以進入任何一個他擁有的鎖同步着的代碼塊。

 

總結:

1、樂觀鎖並未真正加鎖,效率高,一旦鎖的粒度掌握不好,更新失敗的概率會比較高,容易發生業務失敗。

2、悲觀鎖依賴數據庫鎖,效率低,更新失敗的概率比較低。

參考文檔:https://www.jianshu.com/p/d2ac26ca6525

 

 

十三、CAS的理解

1、cas比較替換,是解決多線程環境下使用鎖造成性能損耗的一種機制。

2、CAS操作一般包含三個操作數,內存位置、預期原值、新值。

3、原理

(1) CAS通過調用JNI的代碼實現的。JNI:Java Native InterfaceJAVA本地調用,允許java調用其他語言

(2) 藉助C來調用CPU底層指令實現的

(3) 當前處理器基本支持CAS,只不過不同廠家的實現不一樣。

4、Unsafe類是CAS的核心類,JUC下大量使用了unsafe類,但是不建議開發者使用。

5、CAS缺點

(1) 開銷大:如果併發量大的情況下,如果反覆更新某個值,卻一直更新不成功,會給CPU帶來較大的壓力。

(2) 不能保證代碼塊的原子性:只能保證一個變量的原子性操作,而多個方法或者代碼之間原子性無法保證。

(3) ABA問題:當變量從A修改爲B然後再修改爲A,無法判斷是否修改。CAS操作仍然成功,解決ABA加版本號。

java.util.concurrent包爲了解決這個問題,提供了一個帶有標記的原子引用類"AtomicStampedReference",它可以通過控制變量值的版本來保證CAS的正確性。

 

十四、聊一下synchronized關鍵字的實現原理,以及優化

 

1SynchronizedJava中解決併發問題的一種最常用最簡單的方法 ,他可以確保線程互斥的訪問同步代碼。

2Syn修飾的對象有以下幾種:

1. 修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;2. 修飾一個方法,被修飾的方法稱爲同步方法,其作用的範圍是整個方法,作用的對象是調用這個方法的對象;3. 修改一個靜態的方法,其作用的範圍是整個靜態方法,作用的對象是這個類的所有對象;4. 修改一個類,其作用的範圍是synchronized後面括號括起來的部分,作用主的對象是這個類的所有對象。

3、鎖主要存在四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。鎖只可以升級不能降級,這種策略是爲了提高獲得鎖和釋放鎖的效率。

4synchronized鎖存在於java對象頭裏面,Hotspot虛擬機對象頭主要包含:mark world(標記字段)、類型指針。Mark world存儲對象自身的運行時數據,如:哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向鎖ID、偏向時間戳等等。

5synchronized具有禁止代碼重排序功能

 

參考文檔:https://www.jianshu.com/p/3830e5e0a9e2

https://www.cnblogs.com/shoshana-kong/p/10877564.html

 

十五、談談JDK裏面的鎖(自旋鎖、讀寫鎖、可重入鎖)

自旋鎖:

是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖纔會退出循環。

獲取鎖的線程一直處於活躍狀態,但是並沒有執行任何有效的任務,使用這種鎖會造成busy-waiting

優點:自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減少了不必要的上下文切換

缺點:當線程數不停增加時,性能下降明顯,因爲每個線程都需要執行,佔用CPU時間。

 

如果線程競爭不激烈,並且保持鎖的時間段。適合使用自旋鎖

自旋鎖不支持可重入。

阻塞鎖

1、阻塞鎖改變了線程狀態。Java環境下Thread狀態有:新建狀態、就緒狀態、阻塞狀態、死亡狀態。

2、阻塞鎖讓線程進入阻塞狀態進行等待,當獲得相應的信號(喚醒、睡眠時間到)時,纔可以進入就緒狀態,就緒狀態的所有線程,通過競爭,進入運行狀態。

JAVA中,能夠進入 / 退出、阻塞狀態或包含阻塞鎖的方法有 ,synchronized 關鍵字(其中的重量鎖),ReentrantLock,Object.wait() / notify() ,LockSupport.park() / unpart() 

3、阻塞鎖的優勢:不佔用CPU,競爭激烈的情況下阻塞鎖性能明顯高於自旋鎖

可重入鎖

1、可重入鎖也叫遞歸鎖,指的是同一線程外層函數獲得鎖之後,內層遞歸函數仍然可以獲取該鎖。

2、“獨佔”,就是在同一時刻只能有一個線程獲取到鎖,而其它獲取鎖的線程只能處於同步隊列中等待,只有獲取鎖的線程釋放了鎖,後繼的線程才能夠獲取鎖。“可重入”,就是支持重進入的鎖,它表示該鎖能夠支持一個線程對資源的重複加鎖。在JAVA環境下 ReentrantLock 和synchronized 都是可重入鎖。

3、Synchronized和ReentrantLock區別聯繫

(1) 性能區別:Synchronized優化以前,synchronized的性能是比ReenTrantLock差很多的,但是自從Synchronized引入了偏向鎖,輕量級鎖(自旋鎖)後,兩者的性能就差不多了,在兩種方法都可用的情況下,官方甚至建議使用synchronized,其實synchronized的優化我感覺就借鑑了ReenTrantLock中的CAS技術。都是試圖在用戶態就把加鎖問題解決,避免進入內核態的線程阻塞。

(2) 原理區別:

① Synchronized: 進過編譯,會在同步塊的前後分別形成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器爲0時,鎖就被釋放了。如果獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另一個線程釋放爲止。

② ReentrantLock: 是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下3項:等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待(避免死鎖)、公平鎖、鎖綁定多個條件。

讀寫鎖(ReadWriteLock

1、適用於讀多少寫場景。

2、讀寫鎖允許同一時刻被多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他的寫線程都會被阻塞。

3、公平性選擇:支持非公平性(默認)和公平的鎖獲取方式,吞吐量還是非公平優於公平;

4、重入性:支持重入,讀鎖獲取後能再次獲取,寫鎖獲取之後能夠再次獲取寫鎖,同時也能夠獲取讀鎖;

5、鎖降級:遵循獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成爲讀鎖

6、JUC下的讀寫鎖坑點:當持鎖的是讀線程時,跟隨其後的是一個寫線程,那麼再後面來的讀線程是無法獲取讀鎖的,只有等待寫線程執行完後,才能競爭。

這是jdk爲了避免寫線程過分飢渴,而做出的策略。但有坑點就是,如果某一讀線程執行時間過長,甚至陷入死循環,後續線程會無限期掛起,嚴重程度堪比死鎖。爲避免這種情況,除了確保讀線程不會有問題外,儘量用tryLock,超時我們可以做出響應。

 

互斥鎖

1、互斥 : 就是線程A訪問了一組數據,線程BCD就不能同時訪問這些數據,直到A停止訪問了

2、同步 : 就是ABCD這些線程要約定一個執行的協調順序,比如D要執行,B和C必須都得做完,而B和C要開始,A必須先得做完。

3、Java中synchronized與lock是互斥鎖。

 

悲觀鎖

1、假設會發生併發衝突,屏蔽一切可能違反數據完整性的操作(具有強烈的獨佔和排他性)

2、獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它線程不會造成干擾的情況下執行,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

3、Mysql中用for update

 

樂觀鎖

1、不是真正的鎖,而是一種實現

2、假設不會發生併發衝突,只有在提交操作時檢查是否違反數據完整性,樂觀鎖不能解決髒讀問題。

 

上面有講悲觀鎖樂觀鎖,不再細講。

 

十六、談談AQS

AQS全稱AbstractQueuedSynchronizer,是java併發包中的核心類,諸如ReentrantLock,CountDownLatch等工具內部都使用了AQS去維護鎖的獲取與釋放

AQS內部結構

 

 

類似一個阻塞隊列,當前持有鎖的線程處於head,新進來的無法獲取鎖,則包裝爲一個node節點放入隊尾中。

 

核心屬性

head:當前持有鎖的線程。

tail:阻塞隊列中未獲取鎖的線程。

state:0表示沒有線程持有鎖,當大於0(鎖可重入每次獲取鎖+1)時表示有線程持有鎖。

waitSatus:當大於0時表示當前線程放棄了爭取鎖。prev:前一個節點next:後一個節點thread:所封裝着的線程

 

內部實現

採用模板模式。

下面幾個方法都是鉤子方法,需要子類去實現。

·  tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。沒有實現需要子類去實現。

·  tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。沒有實現需要子類去實現。

·  tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。沒有實現需要子類去實現。

·  tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。沒有實現需要子類去實現。

·  isHeldExclusively():該線程是否正在獨佔資源。只有用到condition才需要去實現它。沒有實現需要子類去實現。

總結

AQS內部通過一個CLH阻塞隊列去維持線程的狀態,並且使用LockSupport工具去實現線程的阻塞和和喚醒,同時裏面大量運用了無鎖的CAS算法去實現鎖的獲取和釋放。

 

十七、談談java內存模型 JMM

JMM是java虛擬機規範定義的,用來屏蔽掉java程序在各種不同硬件和操作系統對內存訪問的差異。

 

JMM內存模型

·  主內存:java虛擬機規定所有的變量(不是程序中的變量)都必須在主內存中產生,爲了方便理解,可以認爲是堆區。可以與前面說的物理機的主內存相比,只不過物理機的主內存是整個機器的內存,而虛擬機的主內存是虛擬機內存中的一部分。

·  工作內存:java虛擬機中每個線程都有自己的工作內存,該內存是線程私有的爲了方便理解,可以認爲是虛擬機棧。可以與前面說的高速緩存相比。線程的工作內存保存了線程需要的變量在主內存中的副本。虛擬機規定,線程對主內存變量的修改必須在線程的工作內存中進行,不能直接讀寫主內存中的變量。不同的線程之間也不能相互訪問對方的工作內存。如果線程之間需要傳遞變量的值,必須通過主內存來作爲中介進行傳遞。

 

 

十七、內存溢出

 

1、棧溢出:方法死循環遞歸調用(StackOverflowError),不斷簡歷線程OutOfMemoryError

2、堆溢出:不斷創建對象,分配對象大於最大堆大小OutOfMemoryError

3、直接內存溢出:分配內存大於JVM限制。

4、方法區溢出:

 

十八、JVM內存結構

 

內存結構分爲:程序計數器、本地方法棧、虛擬機棧、方法區、堆、直接內存。

程序計數器:較小的內存空間,當前線程執行的字節碼的行號指示器,不會OOM

 

本地方法棧:native方法。

 

虛擬機棧:每個線程私有的,在執行某個方法的時候都會打包成一個棧幀,存儲了局部變量表、操作數棧、動態鏈接、方法出口等信息。棧大小缺省1M,用-Xss設置。

 

方法區(1.8改爲元空間metaDataSpace:包含類信息、常量、靜態變量、即使編譯器編譯後的代碼。

 

堆:對象存放區域。所有對象以及數組都在堆上分配,內存中最大的一塊,也是GC最重要的一塊區域。結構:新生代(Eden+2Survivor區)  老年代   永久代(HotSpot有)。

新生代:新創建的對象先放入Eden區。

GC之後,存活的對象由Eden Survivor0進入Survivor1   

再次GC,存活的對象由Eden Survivor1進入Survivor

老年代:對象如果在新生代存活了足夠長的時間而沒有被清理掉(即在幾次Young GC後存活了下來),則會被複制到老年代

如果新創建對象比較大(比如長字符串或大數組),新生代空間不足,則大對象會直接分配到老年代上(大對象可能觸發提前GC,應少用,更應避免使用短命的大對象)

老年代的空間一般比新生代大,能存放更多的對象,在老年代上發生的GC次數也比年輕代少

 

直接內存:直接內存並不是JVM管理的內存,可以這樣理解,直接內存,就是JVM以外的機器內存,比如,你有4G的內存,JVM佔用了1G,則其餘的3G就是直接內存;JDK中有一種基於通道(Channel)和緩衝區(Buffer)的內存分配方式,將由C語言實現的native函數庫分配在直接內存中,用存儲在JVM堆中的DirectByteBuffer來引用。由於直接內存收到本機器內存的限制,所以也可能出現OutOfMemoryError的異常。

 

參考文檔:https://www.toutiao.com/i6789216144449339912/

https://blog.csdn.net/rongtaoup/article/details/89142396

十九、JAVA代碼編譯過程

1.源碼編譯:通過Java源碼編譯器將Java代碼編譯成JVM字節碼(.class文件)

2.類加載:通過ClassLoader及其子類來完成JVM的類加載

3.類執行:字節碼被裝入內存,進入JVM虛擬機,被解釋器解釋執行

 

二十、JVM垃圾收集算法與收集器

標記清除算法:第一階段爲每個對象更新標記位,檢查對象是否存活,第二階段清除階段,對死亡的對象執行清除。

標記整理算法:

複製算法:

 

單線程收集器

多線程收集器

CMS收集器

G1收集器(1.8可以配置)

串行處理器:

         -- 適用情況:數據量比較小(100M左右),單處理器下並且對相應時間無要求的應用。

         -- 缺點:只能用於小型應用。

並行處理器:

          -- 適用情況:對吞吐量有高要求,多CPU,對應用過響應時間無要求的中、大型應用。舉例:後臺處理、科學計算。

          -- 缺點:垃圾收集過程中應用響應時間可能加長。

 併發處理器CMS

          -- 適用情況:對響應時間有高要求,多CPU,對應用響應時間有較高要求的中、大型應用。舉例:Web服務器/應用服務器、電信交換、集成開發環境。

 

二十、JVM調優怎麼做

1  棧是運行時的單位 而堆是存儲的單元

 2 棧解決程序的運行問題,即程序如何執行,或者說如何處理數據,堆解決的是數據存儲的問題,即數據怎麼放,放在哪兒

 

調優步驟:

1、修改GC收集器,選擇G1、CMS

2、打印GC信息,調整新生代、老年代參數減少GC收集頻率

3、另外儘量減少老年代GC回收。

 

常見配置彙總

       堆設置

           -Xms:初始堆大小

           -Xmx:最大堆大小

           -XX:NewSize=n:設置年輕代大小

           -XX:NewRatio=n:設置年輕代年老代的比值。如:爲3,表示年輕代年老代比值爲13,表示EdenSurvivor=3:2,一個Survivor區佔整個年輕代1/5

           -XX:MaxPermSize=n:設置持久代大小

        收集器設置

           -XX:+UseSerialGC:設置串行收集器

           -XX:+UseParallelGC:設置並行收集器

           -XX:+UseParalledlOldGC:設置並行年老代收集器

           -XX:+UseConcMarkSweepGC:設置併發收集器

        垃圾回收統計信息

           -XX:+PrintGC

           -XX:+PrintGCDetails

           -XX:+PrintGCTimeStamps

           -Xloggc:filename

         並行收集器設置

           -XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。

           -XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間

           -XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+N)

         併發收集器設置

           -XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU情況。

           -XX:+ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。

 

 

二十一、finalfinallyfinalize關鍵字的理解

final:用於修飾類、成員變量和成員方法。final修飾的類,不能被繼承(String、StringBuilder、StringBuffer、Math,不可變類),其中所有的方法都不能被重寫(這裏需要注意的是不能被重寫,但是可以被重載,這裏很多人會弄混),所以不能同時用abstract和final修飾類(abstract修飾的類是抽象類,抽象類是用於被子類繼承的,和final起相反的作用);Final修飾的方法不能被重寫,但是子類可以用父類中final修飾的方法;Final修飾的成員變量是不可變的,如果成員變量是基本數據類型,初始化之後成員變量的值不能被改變,如果成員變量是引用類型,那麼它只能指向初始化時指向的那個對象,不能再指向別的對象,但是對象當中的內容是允許改變的。

 

finally:通常和try catch搭配使用,保證不管有沒有發生異常,資源都能夠被釋放(釋放連接、關閉IO流).當try中有return時執行順序:return語句並不是函數的最終出口,如果有finally語句,這在return之後還會執行finally(return的值會暫存在棧裏面,等待finally執行後再返回).

 

 

finalize是object類中的一個方法,子類可以重寫finalize()方法實現對資源的回收。垃圾回收只負責回收內存,並不負責資源的回收,資源回收要由程序員完成,Java虛擬機在垃圾回收之前會先調用垃圾對象的finalize方法用於使對象釋放資源(如關閉連接、關閉文件),之後才進行垃圾回收,這個方法一般不會顯示的調用,在垃圾回收時垃圾回收器會主動調用。

 

二十二、Java線程啓動方式

1、實現Runnable接口優勢:

1)適合多個相同的程序代碼的線程去處理同一個資源

2)可以避免java中的單繼承的限制

3)增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立。

2、繼承Thread類優勢

1)可以將線程類抽象出來,當需要使用抽象工廠模式設計時。

2)多線程同步

3、在函數體使用優勢

1)無需繼承thread或者實現Runnable,縮小作用域。

4、線程池方式

5、實現callable接口允許有返回值

Future:首先,可以用isDone()方法來查詢Future是否已經完成,任務完成後,可以調用get()方法來獲取結果

  如果不加判斷直接調用get方法,此時如果線程未完成,get將阻塞,直至結果準備就緒

 

注:多次start線程會報錯。(線程狀態不對)

二十二、聊聊線程池

   線程池就是首先創建一些線程,它們的集合稱爲線程池。使用線程池可以很好地提高性能,線程池在系統啓動時即創建大量空閒的線程,程序將一個任務傳給線程池,線程池就會啓動一條線程來執行這個任務,執行結束以後,該線程並不會死亡,而是再次返回線程池中成爲空閒狀態,等待執行下一個任務。

 

爲什麼使用線程池:

多線程運行時間,系統不斷的啓動和關閉新線程,成本非常高,會過渡消耗系統資源,以及過渡切換線程的危險,從而可能導致系統資源的崩潰。這時,線程池就是最好的選擇了。池化技術舉例:數據庫連接池、httpClient連接池、tomcat線程池。池化技術主要就是複用資源。

 

ExecutorServiceJava提供的用於管理線程池的類。該類的兩個作用:控制線程數量和重用線程

 

四種線程池(不推薦使用,儘量自己創建):

Executors.newCacheThreadPool():可緩存線程池,先查看池中有沒有以前建立的線程,如果有,就直接使用。如果沒有,就建一個新的線程加入池中,緩存型池子通常用於執行一些生存期很短的異步型任務

Executors.newFixedThreadPool(int n):創建一個可重用固定個數的線程池,以共享的無界隊列方式來運行這些線程。

Executors.newScheduledThreadPool(int n):創建一個定長線程池,支持定時及週期性任務執行

Executors.newSingleThreadExecutor():創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行

 

線程池參數詳解:

public ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,  long keepAliveTime,

                              TimeUnit unit, BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,  RejectedExecutionHandler handler) {

}

corePoolSize:核心線程數。

maximumPoolSize:線程池最大線程數。

keepAliveTime:空閒線程存活時間

unit:時間單位

workQueue:線程池所使用的緩衝隊列

threadFactory:線程池創建線程使用的工廠

handler:線程池對拒絕任務的處理策略

執行原理詳細描述:

1、創建一個線程池,在還沒有任務提交的時候,默認線程池裏面是沒有線程的。當然,你也可以調用prestartCoreThread方法,來預先創建一個核心線程。

2、線程池裏還沒有線程或者線程池裏存活的線程數小於核心線程數corePoolSize時,這時對於一個新提交的任務,線程池會創建一個線程去處理提交的任務。當線程池裏面存活的線程數小於等於核心線程數corePoolSize時,線程池裏面的線程會一直存活着,就算空閒時間超過了keepAliveTime,線程也不會被銷燬,而是一直阻塞在那裏一直等待任務隊列的任務來執行。

3、當線程池裏面存活的線程數已經等於corePoolSize了,這是對於一個新提交的任務,會被放進任務隊列workQueue排隊等待執行。而之前創建的線程並不會被銷燬,而是不斷的去拿阻塞隊列裏面的任務,當任務隊列爲空時,線程會阻塞,直到有任務被放進任務隊列,線程拿到任務後繼續執行,執行完了過後會繼續去拿任務。這也是爲什麼線程池隊列要是用阻塞隊列。

4、當線程池裏面存活的線程數已經等於corePoolSize了,並且任務隊列也滿了,這裏假設maximumPoolSize>corePoolSize(如果等於的話,就直接拒絕了),這時如果再來新的任務,線程池就會繼續創建新的線程來處理新的任務,直到線程數達到maximumPoolSize,就不會再創建了。這些新創建的線程執行完了當前任務過後,在任務隊列裏面還有任務的時候也不會銷燬,而是去任務隊列拿任務出來執行。在當前線程數大於corePoolSize過後,線程執行完當前任務,會有一個判斷當前線程是否需要銷燬的邏輯:如果能從任務隊列中拿到任務,那麼繼續執行,如果拿任務時阻塞(說明隊列中沒有任務),那超過keepAliveTime時間就直接返回null並且銷燬當前線程,直到線程池裏面的線程數等於corePoolSize之後纔不會進行線程銷燬。

5、如果當前的線程數達到了maximumPoolSize,並且任務隊列也滿了,這種情況下還有新的任務過來,那就直接採用拒絕的處理器進行處理。默認的處理器邏輯是拋出一個RejectedExecutionException異常。你也就可以指定其他的處理器,或者自定義一個拒絕處理器來實現拒絕邏輯的處理(比如講這些任務存儲起來)。

JDK提供了四種拒絕策略處理類:AbortPolicy(拋出一個異常,默認的),DiscardPolicy(直接丟棄任務),DiscardOldestPolicy(丟棄隊列裏最老的任務,將當前這個任務繼續提交給線程池),CallerRunsPolicy(交給線程池調用所在的線程進行處理)。

執行原理簡要描述:

1.核心線程是否已滿 沒有滿則繼續執行任務,如果滿了,再來的請求將存儲到隊列裏2.隊列是否已滿 沒有的話核心線程繼續執行任務,如果滿了,線程池增加線程數3.線程池是否已滿,沒有的話線程池繼續處理任務,如果滿了,將啓動拒絕策略核心數 --> 隊列 --> 線程池

 

 

如何確定線程池參數:

需要根據幾個值來決定:

tasks :每秒的任務數,假設爲500~1000

taskcost:每個任務花費時間,假設爲0.1s

responsetime:系統允許容忍的最大響應時間,假設爲1s

1、corePoolSize

(1) threadcount = tasks/(1/taskcost) =tasks*taskcout =  (500~1000)*0.1 = 50~100 個線程。corePoolSize設置應該大於50。

(2) 根據8020原則,如果80%的每秒任務數小於800,那麼corePoolSize設置爲80即可

2、queueCapacity = (coreSizePool/taskcost)*responsetime

(1) 計算可得 queueCapacity = 80/0.1*1 = 80。意思是隊列裏的線程可以等待1s,超過了的需要新開線程來執行

(2) 切記不能設置爲Integer.MAX_VALUE,這樣隊列會很大,線程數只會保持在corePoolSize大小,當任務陡增時,不能新開線程來執行,響應時間會隨之陡增。

3、maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)

計算可得 maxPoolSize = (1000-80)/10 = 92

(最大任務數-隊列容量)/每個線程每秒處理能力 = 最大線程數

4、rejectedExecutionHandler:根據具體情況來決定,任務不重要可丟棄,任務重要則要利用一些緩衝機制來處理

5、keepAliveTime和allowCoreThreadTimeout採用默認通常能滿足

 

二十三、線程狀態

1.初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。2.運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲“運行”。線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片後變爲運行中狀態(running)。3.阻塞(BLOCKED):表示線程阻塞於鎖。4.等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。5.超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。

6.終止(TERMINATED):表示該線程已經執行完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章