JAVA多執行緒併發

顏小小發表於2020-12-21

1. JAVA執行緒實現、建立方式

  1. 繼承Thread類
    Thread類本質上是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟動執行緒的唯一方 法就是通過Thread類的start()例項方法。start()方法是一個native方法,它將啟動一個新線 程,並執行run()方法。
  2. 實現Runnable介面
    如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個 Runnable介面
  3. Callable介面
    有返回值的任務必須實現Callable介面,類似的,無返回值的任務必須Runnable介面。執行 Callable任務後,可以獲取一個Future的物件,在該物件上呼叫get就可以獲取到Callable任務 返回的Object了,再結合執行緒池介面ExecutorService就可以實現傳說中有返回結果的多執行緒 了。

2. 執行緒池的方式

執行緒池:執行緒池做的工作主要是控制執行的執行緒的數量,處理過程中將任務放入佇列,然後線上程建立後 啟動這些任務,如果執行緒數量超過了大數量超出數量的執行緒排隊等候,等其它執行緒執行完畢, 再從佇列中取出任務來執行。他的主要特點為:執行緒複用;控制大併發數;管理執行緒。
執行緒複用:每一個 Thread 的類都有一個 start 方法。 當呼叫start啟動執行緒時Java虛擬機器會呼叫該類的 run 方法。 那麼該類的 run() 方法中就是呼叫了 Runnable 物件的 run() 方法。 我們可以繼承重寫 Thread 類,在其 start 方法中新增不斷迴圈呼叫傳遞過來的 Runnable 物件。 這就是執行緒池的實 現原理。迴圈方法中不斷獲取 Runnable 是用 Queue 實現的,在獲取下一個 Runnable 之前可以 是阻塞的。
Java 裡面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 並不是一個執行緒池,而 只是一個執行執行緒的工具。真正的執行緒池介面是ExecutorService。
組成部分: 執行緒池管理器:用於建立並管理執行緒池 2. 工作執行緒:執行緒池中的執行緒 3. 任務介面:每個任務必須實現的介面,用於工作執行緒排程其執行 4. 任務佇列:用於存放待處理的任務,提供一種緩衝機制

  1. newCachedThreadPool
    建立一個可根據需要建立新執行緒的執行緒池,但是在以前構造的執行緒可用時將重用它們。對於執行 很多短期非同步任務的程式而言,這些執行緒池通常可提高程式效能。呼叫 execute 將重用以前構造 的執行緒(如果執行緒可用)。如果現有執行緒沒有可用的,則建立一個新執行緒並新增到池中。終止並 從快取中移除那些已有 60 秒鐘未被使用的執行緒。因此,長時間保持空閒的執行緒池不會使用任何資 源。
  2. newFixedThreadPool
    建立一個可重用固定執行緒數的執行緒池,以共享的無界佇列方式來執行這些執行緒。在任意點,在大 多數 nThreads 執行緒會處於處理任務的活動狀態。如果在所有執行緒處於活動狀態時提交附加任務, 則在有可用執行緒之前,附加任務將在佇列中等待。如果在關閉前的執行期間由於失敗而導致任何 執行緒終止,那麼一個新執行緒將代替它執行後續的任務(如果需要)。在某個執行緒被顯式地關閉之 前,池中的執行緒將一直存在。
  3. newScheduledThreadPool
    建立一個執行緒池,它可安排在給定延遲後執行命令或者定期地執行
  4. newSingleThreadExecutor
    Executors.newSingleThreadExecutor()返回一個執行緒池(這個執行緒池只有一個執行緒),這個執行緒 池可以線上程死後(或發生異常時)重新啟動一個執行緒來替代原來的執行緒繼續執行下去!

3. 執行緒的生命週期

當執行緒被建立並啟動以後,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態。 線上程的生命週期中,它要經過新建(New)、就緒(Runnable)、執行(Running)、阻塞 (Blocked)和死亡(Dead)5種狀態。
新建(New):使用new關鍵字建立了一個執行緒之後
就緒(Runnable):當執行緒物件呼叫了start()方法之後
執行(Running):就緒狀態的執行緒獲得了CPU,開始執行run()方法的執行緒執行體
阻塞 (Blocked):

  1. 等待阻塞:執行緒執行o.wait()方法,JVM會把該執行緒放入等待佇列
  2. 同步阻塞:執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該線 程放入鎖池(lock pool)中
  3. 其他阻塞:執行緒執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時, JVM會把該執行緒置為阻塞狀態。
    死亡(Dead):
  4. 正常結束:run()或call()方法執行完成,執行緒正常結束。
  5. 異常結束:執行緒丟擲一個未捕獲的Exception或Error
  6. 呼叫stop:stop()方法來結束該執行緒—該方法通常容易導致死鎖

4. 執行緒結束4種方式

  1. 正常結束:程式執行結束,執行緒自動結束
  2. 退出標誌結束:設一個boolean型別的標誌,並通過設定這個標誌為true或false來控制while 迴圈是否退出
  3. Interrupt結束:
    (1)執行緒處於阻塞:如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時, 會使執行緒處於阻塞狀態。當呼叫執行緒的 interrupt()方法時,會丟擲 InterruptException 異常,先捕獲該異常,然後 break 跳出迴圈狀態,結束這個執行緒的執行。
    (2)執行緒未處於阻塞:使用isInterrupted()判斷執行緒的中斷標誌來退出迴圈
  4. stop結束(執行緒不安全):thread.stop()呼叫之後,建立子執行緒的執行緒就會丟擲 ThreadDeatherror 的錯誤,並且會釋放子 執行緒所持有的所有鎖。一般任何進行加鎖的程式碼塊,都是為了保護資料的一致性,如果在呼叫 thread.stop()後導致了該執行緒所持有的所有鎖的突然釋放(不可控制),那麼被保護資料就有可能呈 現不一致性,其他執行緒在使用這些被破壞的資料時,有可能導致一些很奇怪的應用程式錯誤。
    (1)sleep與wait的區別
    對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於 Object類中的。
    sleep()方法導致了程式暫停執行指定的時間,讓出cpu該其他執行緒,但是他的監控狀態依然 保持者,當指定的時間到了又會自動恢復執行狀態。
    在呼叫sleep()方法的過程中,執行緒不會釋放物件鎖。
    而當呼叫wait()方法的時候,執行緒會放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此 物件呼叫notify()方法後本執行緒才進入物件鎖定池準備獲取物件鎖進入執行狀態。
    (2)start與run的區別
    start()方法來啟動執行緒,真正實現了多執行緒執行。這時無需等待 run 方法體程式碼執行完畢,可以直接繼續執行下面的程式碼。
    通過呼叫 Thread 類的 start()方法來啟動一個執行緒, 這時此執行緒是處於就緒狀態, 並沒有執行。
    方法 run()稱為執行緒體,它包含了要執行的這個執行緒的內容,執行緒就進入了執行狀態,開始運 行run函式當中的程式碼。 Run方法執行結束, 此執行緒終止。然後CPU再排程其它執行緒。

5. 執行緒基本方法

執行緒相關的基本方法有wait,notify,notifyAll,sleep,join,yield等。
執行緒等待(wait):會釋放物件的鎖
執行緒睡眠(sleep):不會釋放當前佔有的鎖
執行緒讓步(yield):當前執行緒讓出 CPU 執行時間片
執行緒中斷(interrupt):給這個執行緒一個通知訊號,會影響這個執行緒內部的一箇中斷標識位。這個執行緒本身狀態不會變
執行緒喚醒(notify):喚醒在此物件監視器上等待的單個執行緒
join:等待其他執行緒終止,很多情況下,主執行緒生成並啟動了子執行緒,需要用到子執行緒返回的結果,也就是需要主執行緒需要 在子執行緒結束後再結束

相關文章