【STM32F407】第9章 RL-TCPnet V7.X除錯方法(Event Recorder和串列埠兩種)
在一個應用程式中,我們需要多次使用執行緒,也就意味著,我們需要多次建立並銷燬執行緒。而建立並銷燬執行緒的過程勢必會消耗記憶體。而在Java中,記憶體資源是及其寶貴的,所以,我們就提出了執行緒池的概念。
執行緒池:Java中開闢出了一種管理執行緒的概念,這個概念叫做執行緒池,從概念以及應用場景中,我們可以看出,執行緒池的好處,就是可以方便的管理執行緒,也可以減少記憶體的消耗。
執行緒池的優勢:
執行緒池做的工作主要是控制執行的執行緒的數量,處理過程中將任務放入佇列,然後線上程建立後啟動這些任務,如果執行緒數量超過了最大數量超出數量的執行緒排隊等待,等其它執行緒執行完畢,再從佇列中取出任務來執行。
主要特點:執行緒複用、控制最大併發數、管理執行緒。
1.降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
2.提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行。
3.提高執行緒的課管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。
Java中的執行緒池是通過Executor框架來實現的,而我們建立時,一般使用它的子類:ThreadPoolExecutor。
java中提供了一個靜態工廠方法來建立不同的執行緒池:Executors
通過靜態方法創建出的執行緒都實現了ExecutorService介面
常用的方法包括:
FixedThreadPool(int threads):定長的執行緒池,有核心執行緒,核心執行緒的即為最大的執行緒數量,沒有非核心執行緒
SingleThreadPool():只有一條執行緒來執行任務,適用於有順序的任務的應用場景。
CachedThreadPool():可快取的執行緒池,該執行緒池中沒有核心執行緒,非核心執行緒的數量為Integer.max_value,就是無限大,當有需要時建立執行緒來執行任務,沒有需要時回收執行緒,適用於耗時少,任務量大的情況。
SecudleThreadPool:週期性執行任務的執行緒池,按照某種特定的計劃執行執行緒中的任務,有核心執行緒,但也有非核心執行緒,非核心執行緒的大小也為無限大。適用於執行週期性的任務。
WorkStealingPool:java8新增,使用目前機器上可用的處理器作為它的並行級別。
常用的執行緒池建立方法示例如下:
/** * 第四種獲取執行緒的方式-執行緒池 */ public class ThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool=Executors.newFixedThreadPool(5);//一池5個執行緒 //ExecutorService threadPool=Executors.newSingleThreadExecutor();//一池一個執行緒 //ExecutorService threadPool=Executors.newCachedThreadPool();//一池N執行緒,根據情況來建立 try{ //模擬十個使用者來辦理業務 for(int i=1;i<=10;i++){ //submit有返回值 Future future=threadPool.submit( () -> { System.out.println(Thread.currentThread().getName()+" 辦理業務"); return "辦理業務成功"; }); System.out.println("獲取結果"+future.get()); //execute無返回值 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+" 辦理業務"); }); } }catch (Exception e){ e.printStackTrace(); }finally { threadPool.shutdown(); } } }
執行緒池的底層就是ThreadPoolExecutor類。
1.Executors.FixedThreadPool(int),執行長期的任務,效能好很多
主要特點如下:
1).建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
2).newFixedThreadPool建立的執行緒池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue。
2.Executors.SingleThreadPool( ),一個任務一個任務執行的場景
主要特點如下:
1).建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序執行。
2).newSingleThreadExecutor將corePoolSize和maximumPoolSize都設定為1,它使用的LinkedBlockingQueue。
3.Executors.CachedThreadPool( ),適用:執行很多短期非同步的小程式或者負載較輕的伺服器。
主要特點如下:
1).建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
2).newCachedThreadPool將corePoolSize設定為0,將maximumPoolSize設定為Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就建立執行緒執行,當執行緒空閒超過60秒,就銷燬執行緒。
七大引數
1.corePoolSize:執行緒池中的常駐核心執行緒數
在建立了執行緒池後,當有請求任務來之後,就會安排池中的執行緒去執行請求任務,近似理解為今日當值執行緒;
當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中。
2.maximumPoolSize:執行緒池能夠容納同時執行的最大執行緒數,此值必須大於等於1
3.keepAliveTime:多餘的空閒執行緒的存活時間。當前執行緒池數量超過corePoolSize時,當空閒時間達到keepAliveTime值時,多餘空閒執行緒會被銷燬直到只剩下corePoolSize個執行緒為止。
預設情況下:只有當執行緒池中的執行緒數大於corePoolSize時keepAliveTime才會起作用,直到執行緒池中的執行緒數不大於corePoolSize。
4.unit:keepAliveTime的單位
5.workQueue:任務佇列,被提交單尚未被執行的任務。
6.threadFactory:表示生成執行緒池中工作執行緒的執行緒工廠,用於建立執行緒一般用預設的即可。
7.handler:拒絕策略,表示當佇列滿了並且工作執行緒大於等於執行緒池的最大執行緒數(maximumPoolSize)時如何來拒絕請求執行的runnable的策略。
拒絕策略是當等待佇列也已經排滿了,再也塞不下新任務了,同時,執行緒池中的max執行緒也達到了了,無法繼續為新任務服務。這時候我們就需要拒絕策略機制合理的處理這個問題。
JDK內建的拒絕策略
1)AbortPolicy(預設):直接丟擲RejectedExecutionException異常阻止系統正常執行。
2)CallerRunsPolicy:"呼叫者執行"一種調節機制,該策略既不會拋棄任務,也不會丟擲異常,而是將某些任務回退到呼叫者,從而降低新任務的流量。
3)DiscardOldestPolicy:拋棄佇列中等待最久的任務,然後把當前任務加入佇列中嘗試再次提交當前任務。
4)DiscardPolicy:直接丟棄任務,不予任何處理也不丟擲異常。如果允許任務丟失,這是最好的一種方案。
以上內建拒絕策略均實現了RejectedExecutionHandler介面
執行緒池的底層工作原理
1.在建立了執行緒池後,等待提交過來的任務請求。
2.當呼叫execute()方法新增一個請求任務時,執行緒池會做如下判斷:
1)如果正在執行的執行緒數量小於corePoolSize,那麼馬上建立執行緒執行這個任務;
2)如果正在執行的執行緒數量大於或等於corePoolSize,那麼將這個任務放入佇列;
3)如果這時候佇列滿了且正在執行的執行緒數量還小於maximumPoolSize,那麼還是要建立非核心執行緒立刻執行這個任務;
4)如果佇列滿了且正在執行的執行緒數量大於或等於maximumPoolSize,那麼執行緒池會啟動飽和拒絕策略來執行。
3.當一個執行緒完成任務時,它會從佇列中取下一個任務來執行。
4.當一個執行緒無事可做超過一定的時間(keepAliveTime)時,執行緒池會判斷:
如果當前執行的執行緒數大於corePoolSize,那麼這個執行緒就被停掉。
所以執行緒池的所有任務完成後它最終會收縮到corePoolSize的大小。
實際開發中一般手動寫執行緒池,這樣合理利用執行緒資源,避免資源耗盡的。
手寫執行緒池示例:
public class MyThreadPoolDemo { public static void main(String[] args) { //自定義執行緒池 ExecutorService threadPool= new ThreadPoolExecutor( 2, 5, 1L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(3),//自定義阻塞佇列長度為3 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()//自定義拒絕策略,預設為AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy ); try{ //模擬十個使用者來辦理業務,每個使用者都是一個來自外部的請求執行緒 for(int i=1;i<=10;i++){ //無返回值 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"\t 辦理業務"); }); } }catch (Exception e){ e.printStackTrace(); }finally { //關閉執行緒池 threadPool.shutdown(); } }
使用預設的拒絕策略,能夠訪問的上限數=當最大執行緒數(maximumPoolSize) + 任務佇列長度,當超過就會報java.util.concurrent.RejectedExecutionException異常。
死鎖編碼及定位分析
死鎖是指兩個或兩個以上的程序在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力干涉那它們都將無法推進下去。如果系統資源充足,程序的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。
死鎖示例:
/** * 死鎖是指兩個或者兩個以上的程序在執行過程中, * 因爭奪資源而造成的一種相互等待的現象, * 若無外力干涉,那它們就無法推進下去,從而造成死鎖現象。 */ public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new DeadLockResource(lockA, lockB), "ThreadA").start(); new Thread(new DeadLockResource(lockB, lockA), "ThreadB").start(); } } /** * 執行緒操作資源類 */ class DeadLockResource implements Runnable { private String lockA; private String lockB; public DeadLockResource(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "\t 擁有" + lockA + ",試圖獲取" + lockB); System.out.println("----------*****************----------"); synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "\t 擁有" + lockB + ",試圖獲取" + lockA); } } } }
ThreadA持有lockA,試圖獲取lockB,而ThreadB持有lockB,試圖獲取lockA。造成兩個執行緒相互等待,又沒有外力干涉,無法推進下去,最終導致死鎖現象。
產生死鎖的主要原因
1.系統資源不足
2.程序執行推進的順序不合適
3.資源分配不當
死鎖解決
Linux環境下檢視程序:ps -ef | grep java
Windows環境下檢視java程序:jps -l
Windows環境下檢視死鎖棧資訊:jstack +程序號
1.jps命令定位程序號
2.jstack找到死鎖檢視
只有停下程式,找到對應業務程式碼進行修改。
合理配置執行緒池:
首先得知道伺服器cpu核心數
獲取cpu核心數
Runtime.getRuntime().availableProcessors()
業務分為cpu密集型還是IO密集型,根據實際業務來配置
1.CPU密集型
CPU密集的意思是該任務需要大量的運算,而沒有阻塞,CPU一直全速執行。
CPU密集任務只有在真正的多核CPU上才可能得到加速(通過多執行緒),而在單核CPU上,無論你開幾個模擬的多執行緒,該任務都不可能得到加速,因為CPU總的運算能力就那些。
CPU密集型任務配置儘可能少的執行緒數量:
一般公式:CPU核數+1個執行緒的執行緒池
2.IO密集型
1) 由於IO密集型任務執行緒並不是一直在執行任務,則應配置儘可能多的執行緒,如CPU核數*2
2) IO密集型,即該任務需要大量的IO,即大量的阻塞。在單執行緒上執行IO密集型的任務會導致浪費大量的CPU運算能力浪費在等待。所以在IO密集型任務中使用多執行緒可以大大的加速程式執行,即時在單核CPU上,這種加速主要就是利用了被浪費掉的阻塞時間。
IO密集型時,大部分執行緒都阻塞,故需要多配置執行緒數:
參考公式:CPU核數/1-阻塞係數(阻塞係數在0.8~0.9之間),比如8核CPU:8/1-0.9=90個執行緒數