1. 程式人生 > >多執行緒、高併發

多執行緒、高併發

執行緒與程序

  • 執行緒是CPU排程和分配的基本單位,程序是資源分配和排程的基本單位
  • 一個程序中可以包括多個執行緒,執行緒共享整個程序的資源(暫存器、堆疊、上下文),執行緒間要進行同步和互斥

區別

  • 程序結束後它擁有的所有執行緒都將銷燬,執行緒的結束不會影響同個程序中的其他執行緒結束
  • 執行緒是輕兩級的程序,建立和銷燬所需要的時間比程序多,作業系統中的功能執行需要建立執行緒

協程

  1. 協程協程是一種使用者態的輕量級執行緒,由使用者程式控制排程、切換開銷更小,作業系統完全感知不到
  2. 最大限度地利用cpu

執行緒的工作原理 記憶體模型

jvm有一個主存main memory,而每個執行緒有自己的(工作記憶體)working memory,一個執行緒對一個變數所有操作,都要在工作記憶體裡建立一個副本,操作完之後再寫入主存;

(1) 從主存複製變數到工作記憶體 (read and load) 

(2) 執行程式碼,操作變數 (use and assign) 

(3) 工作記憶體資料重新整理到主存 (store and write)

執行緒安全:

  1. 當多個執行緒訪問某個類,其始終能表現出正確的行為
  2. 採用了加鎖機制,當一個執行緒訪問該類的某個資料時,進行保護,限制其他執行緒訪問,直到鎖釋放

多執行緒的優點: 

1、提高應用程式響應:當一個操作耗時很長時,整個系統都會等待這個操作,使用多執行緒技術,將耗時長的操作置於一個新的執行緒,可以避免這種情況。 

2、使多CPU系統有效,充分利用:作業系統會保證當執行緒數不大於CPU數目時,不同的執行緒運行於不同的CPU上。 

3、改善程式結構:複雜的程序可以分為多個執行緒,成為幾個獨立的執行部分,程式利於理解和修改

同步 非同步 阻塞 非阻塞

程序呼叫,IO,方法:

同步:功能呼叫後,沒有得到結果前,呼叫不返回

非同步:通過狀態、回撥函式處理呼叫,無需立刻返回

執行緒:

阻塞:結果返回之前,當前執行緒會被掛起,讓出cpu時間,僅在得到結果之後才繼續執行

非阻塞:即使不能立刻得到結果,也不會掛起執行緒

多執行緒同步方式

  • synchronized關鍵字修飾【類、靜態變數、方法、程式碼塊】 synchronized (this) {  }
  • volatile關鍵字 可見性,免鎖
  • 重入鎖 ReentrantLock

如何建立執行緒

Java使用Thread類代表執行緒,所有的執行緒物件都Thread類或其子類的例項

3種方式來建立執行緒:

1)繼承Thread類建立執行緒

  • 並重寫該類的run()方法---執行緒執行體
  • 呼叫執行緒的start()方法

2)實現Runnable介面建立執行緒 適合多繼承

  • 建立Runnable實現類的例項
  • 這個例項作為Thread的引數來建立Thread物件
  • start()方法來啟動執行緒
  • 若自定義類需要繼承其他類,實現Runnable介面

3)使用Callable和Future建立執行緒

  1. call()方法可以有返回值,任務執行完畢之後得到任務的執行結果
  2. call()方法可以宣告丟擲異常

java5提供了Future介面來代表Callable接口裡的call()方法的返回值,提供FutureTask實現類,實現類實現了Future介面,並實現了Runnable介面

既能當做一個Runnable直接被Thread執行,也能作為Future用來得到Callable的計算結果

啟動執行緒時,Start與run的區別是什麼?    35

1)start方法

  start()用來啟動一個執行緒,當呼叫start方法後,系統才會開啟一個新的執行緒來執行使用者定義的子任務,在這個過程中,會為相應的執行緒分配需要的資源。

2)run方法

  run()方法是不需要使用者來呼叫的,當start方法啟動執行緒後,執行緒獲得CPU執行時間,便進入run方法體去執行具體的任務。繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。

ThreadLocal的實現  

  1. ThreadLocal維護變數時,為每個使用該變數的執行緒提供獨立的變數副本
  2. 每一個執行緒可以獨立地改變自己的副本,不影響其它執行緒的副本

應用:資料庫連線的獨立建立與關閉

每個執行緒中都有一個connect變數

Spring使用ThreadLocal解決執行緒安全問題:

同一執行緒貫通MVC三層,將一些非執行緒安全的變數以ThreadLocal存放,在同一次請求響應的呼叫執行緒中,所有關聯的物件引用到的都是同一個變數。

java中的執行緒有幾種狀態?  新建狀態  就緒狀態 執行狀態 阻塞狀態 終止狀態

執行緒從建立、執行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、執行狀態、阻塞狀態及死亡狀態。

所謂阻塞狀態是:正在執行的執行緒沒有執行結束,暫時讓出CPU

1.新建狀態(New): 

當用new操作符建立一個執行緒時, 例如new Thread(r),執行緒還沒有開始執行,此時執行緒處在新建狀態

2.就緒狀態(Runnable)

        執行緒物件呼叫start()方法即啟動了執行緒,start()方法建立執行緒執行的系統資源,並排程執行緒執行run()方法。當start()方法返回後,執行緒就處於就緒狀態

       於就緒狀態的執行緒必須同其他執行緒競爭CPU時間,只有獲得CPU時間才可以執行執行緒。多個處於就緒狀態的執行緒由執行緒排程程式(thread scheduler)排程

3.執行狀態(Running)

        當執行緒獲得CPU時間後,進入執行狀態,真正開始執行run()方法.

4. 阻塞狀態(Blocked) **********

所謂阻塞狀態是:正在執行的執行緒被掛起,暫時讓出CPU,其他處於就緒狀態的執行緒就可以獲得CPU時間,進入執行狀態

        執行緒執行過程中,可能由於各種原因進入阻塞狀態:

        1>sleep方法進入睡眠狀態;

        2>I/O上阻塞的操作,即該操作在輸入輸出操作完成之前不會返回

        3>試圖得到一個鎖,而該鎖正被其他執行緒持有;

        4>等待觸發條件

TIMED_WAITING,對應"阻塞"狀態,代表此執行緒正處於有限期的主動等待中,要麼有人喚醒它,要麼等待夠了一定時間之後,才會再次進入就緒狀態  

5. 終止狀態(Dead)

        有兩個原因會導致執行緒死亡:

        1) run方法正常退出而自然死亡,

        2) 一個未捕獲的異常終止了run方法,執行緒猝死。

        為了確定執行緒在當前是否存活著(執行、阻塞),使用isAlive方法:如果是可執行或被阻塞,返回true; new狀態或不可執行, 或死亡,返回false

看哪些執行緒處於阻塞狀態 jstack  ps -ef   ps -aux

jstack用於打印出給定的java程序ID或core file或遠端除錯服務的Java堆疊資訊

什麼是阻塞佇列

阻塞新增

當阻塞佇列元素已滿時,佇列會阻塞加入元素的執行緒,直佇列元素不滿時才重新喚醒執行緒執行加入操作。

阻塞刪除

佇列元素為空時,刪除佇列元素的執行緒將被阻塞,直到佇列不為空再執行刪除操作

java中的執行緒池實現 4種 FixedThreadPool底層使用的是什麼任務佇列?    39

執行緒池的介面:Executors介面;實現類ThreadPoolExecutor類

ThreadPoolExecutor通過構造方法的引數,構造不同配置的執行緒池,除了ScheduledThreadPool

1.FixedThreadPool( 3 ) 執行緒數

說明:指定執行緒數的執行緒池,使用LinkedBlockingQuene作為阻塞佇列

特點:當執行緒池沒有可執行任務時,也不會釋放執行緒。

2.CachedThreadPool( ) 無引數

說明:可以快取執行緒的執行緒池,預設快取60s,執行緒池的執行緒數可達到Integer.MAX_VALUE,SynchronousQueue作為阻塞佇列(不儲存元素);

特點:在沒有任務執行時,執行緒的空閒時間超過keepAliveTime 60s,會自動釋放執行緒資源;

當提交新任務時,如果沒有空閒執行緒,則建立新執行緒執行任務;否則複用未超過60s的空閒執行緒

使用時注意控制併發的任務數,防止因建立大量的執行緒導致而降低效能

3.SingleThreadExecutor( )

說明:初始化只有一個執行緒的執行緒池,內部使用LinkedBlockingQueue作為阻塞佇列。

特點:保證所提交任務的順序執行,如果該執行緒異常結束,會重新建立一個新的執行緒繼續執行任務,

4.ScheduledThreadPool() }, 1, 3, TimeUnit.SECONDS); 定義在ExecutorService介面中

特定:延遲(定時)執行、週期執行;延遲1秒後,每3秒執行一次

ThreadPoolExecutor引數含義: 7 個

<1>corePoolSize:執行緒池中核心執行緒的數量,預建立執行緒; 作用:頻繁建立執行緒和銷燬執行緒需要時間,

  1. 初始執行緒池中沒有執行緒,等待有任務到來才建立執行緒
  2. 少於corePoolSize的一直建立執行緒,即使有執行緒空閒
  3. 任務數多於corePoolSize的任務放入阻塞佇列;
  4. 阻塞佇列滿,繼續建立執行緒直到maximumPoolSize

<2>maximumPoolSize:執行緒池允許建立的最大執行緒數;

  1. 阻塞佇列滿,已建立的執行緒數小於maximumPoolSize,建立新的執行緒來處理阻塞佇列中的任務,不超過maximumPoolSize;

<3>keepAliveTime:工作執行緒空閒之後繼續存活的時間,預設一直存活

  1. 預設執行緒數大於corePoolSize時起作用,執行緒數目小於corePoolSize,一直存活;

<4>unit:引數keepAliveTime的時間單位;如:TimeUnit.SECONDS

<5>workQueue:阻塞佇列;儲存等待執行的任務,有四種阻塞佇列型別,

  1. ArrayBlockingQueue(基於陣列的有界阻塞佇列)
  2. LinkedBlockingQueue(基於連結串列結構的阻塞佇列)
  3. SynchronousQueue(不儲存元素的阻塞佇列) 無界佇列
  4. PriorityBlockingQueue(具有優先順序的阻塞佇列)

<6>threadFactory:用於建立執行緒的執行緒工廠; 作用:統一在建立執行緒時的引數,如是否守護執行緒。

<7>handler:飽和策略;阻塞佇列滿,沒有空閒執行緒,執行緒數目已達到最大數量,佇列處於飽和狀態,

1.Abort策略:預設策略,新任務提交時直接丟擲未檢查的異常RejectedExecutionException,該異常可由呼叫者捕獲。

2.CallerRuns策略:在呼叫者的執行緒中執行新的任務,既不拋棄任務也不丟擲異常。

3.Discard策略: 新任務被拋棄。

4.DiscardOldest策略:舊任務被拋棄。(不適合工作佇列為優先佇列場景,否則優先順序高的會被拋棄)

執行緒池的狀態 5 種

1、RUNNING:會接收新任務、處理阻塞佇列中的任務;

2、SHUTDOWN: 不會接收新任務,會處理阻塞佇列中的任務;

3、STOP :不接收,不處理任務,會中斷正在執行的任務;

4、TIDYING :對執行緒進行整理優化;

5、TERMINATED: 停止工作;

向執行緒池提交任務(2種)

有兩種方式:

1.executor.execute (new Runnable()

重複以下程式碼新增任務到執行緒池中

ExecutorService executor=Executors.newFixedThreadPool(2);

executor.execute(new Runnable() { @Override public void run() {具體任務 }

});

2.executor.submit(new Runnable()

Future物件來判斷當前的執行緒是否執行完畢,new Runnable() 無法返回資料資訊

Future future = executor.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); //如果任務結束執行則返回 null System.out.println("future.get()=" + future.get());

3.executor.submit(new Callable()

Future物件,傳遞的引數為Callable物件,new Callable()可以返回資料資訊

Future future = executor.submit(new Callable(){ public Object call() throws Exception { return "Callable Result"; } }); System.out.println("future.get() = " + future.get()); //上述樣例程式碼會輸出如下結果: //future.get() = Callable Result

execute()內部實現

1.首次通過workCountof()獲知當前執行緒池中的執行緒數,如果小於corePoolSize, 就通過addWorker()建立執行緒並執行該任務;否則,將該任務放入阻塞佇列;

2. 如果能成功將任務放入阻塞佇列中,  如果當前執行緒池是非RUNNING狀態,則將該任務從阻塞佇列中移除,然後執行reject()處理該任務;

如果當前執行緒池處於RUNNING狀態,則需要再次檢查執行緒池(因為可能在上次檢查後,有執行緒資源被釋放),是否有空閒的執行緒;如果有則執行該任務;

3、如果不能將任務放入阻塞佇列中,說明阻塞佇列已滿;那麼將通過addWoker()嘗試建立一個新的執行緒去執行這個任務;

如果addWoker()執行失敗,說明執行緒池中執行緒數達到maxPoolSize,則執行reject()處理任務;

 sumbit()內部實現

1.將提交的Callable任務會被封裝成了FutureTask物件

2.FutureTask類實現了Runnable介面,通過Executor.execute()提交FutureTask到執行緒池,最終執行FutureTask的run方法

比較:兩個方法都可以向執行緒池提交任務

  • execute()方法的返回型別是void,它定義在Executor介面中
  • submit()方法可返回持有計算結果的Future物件;擴充套件了Executor介面,定義在ExecutorService介面中,

其它執行緒池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。 

執行緒池的關閉(2種) shutdown()和shutdownNow()

  shutdown():不會立即終止執行緒池,要等所有任務快取佇列中的任務都執行完後才終止,不會接受新的任務

  shutdownNow():立即終止執行緒池,並嘗試打斷正在執行的任務,並清空任務快取佇列,返回尚未執行的任務

 執行緒池容量的動態調整

ThreadPoolExecutor提供了動態調整執行緒池容量大小:setCorePoolSize() 和 setMaximumPoolSize()

Java執行緒池使用有界佇列實現時,飽和策略有哪些?    35

執行緒池會將提交的任務先置於工作佇列

無界佇列不存在飽和的問題,但是其問題是當請求持續高負載的話,任務會無腦的加入工作佇列,那麼很可能導致記憶體等資源溢位或者耗盡

有界佇列不會帶來高負載導致的記憶體耗盡的問題,但是有引發工作佇列已滿情況下,新提交的任務如何管理的難題,這就是執行緒池工作佇列飽和策略要

解決的問題

當工作佇列滿了,不同策略的處理方式為:

1.Abort策略:預設策略,新任務提交時直接丟擲未檢查的異常RejectedExecutionException,該異常可由呼叫者捕獲。

2.CallerRuns策略:在呼叫者的執行緒中執行新的任務,既不拋棄任務也不丟擲異常。

3.Discard策略: 新任務被拋棄。

4.DiscardOldest策略:舊任務被拋棄。(不適合工作佇列為優先佇列場景,否則優先順序高的會被拋棄)