java 基礎回顧 - 線程基礎

1. 線程的狀態

java 中線程狀態分爲 6 種

  1. New初始: 新創建了一個線程對象, 但是還沒有調用 start() 方法.
  2. Runnable運行: Java 線程中將就緒(ready) 和運行中 (running) 兩種狀態都成爲 "運行".
    線程對象創建後, 其他線程(比如 main 線程) 調用了該對象的 start() 方法, 該狀態的線程位於可運行線程池中, 等待被線程調度選中, 獲取 CPU 的使用權, 此時就處於就緒狀態(ready). 就緒狀態的線程在獲得 CPU 時間片後變爲運行中(running) 狀態.
  3. Blocked阻塞: 表示線程阻塞於鎖
  4. Waiting等待: 進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)後纔可變爲Runnable運行狀態.
  5. Timed_Waiting超時等待: 這個狀態不同於 Waiting, 它可以在指定的時間後自行變爲Runnable運行狀態.
  6. Terminated終止: 表示該線程已經執行完畢.

2. 線程的啓動方式

  • xxx extends Thread 然後調用 xxx.start
  • xxx implements Runnable 然後交給 Thread 運行.
  • 區別: Thread 纔是 Java 中對線程唯一抽象, Runnable 只是對任務(業務邏輯)的抽象. Thread可以接受任意一個 Runnable 的實例並執行.

start() 方法只是將一個線程進入就緒隊列等待分配 CPU, 分到 CPU 後纔會去調用實現的 run()方法. start() 方法不能重複調用, 否則拋出異常.

run 方法是業務邏輯實現的地方, 可以重複執行, 也可以被單獨調用.

2. 線程的終止

  • 線程自然終止

線程的中止要麼是 run 執行完成了, 要麼是拋出了一個未處理的異常,從而導致線程提前結束.

  • 使用 stop() 方法

不建議使用, stop()方法在終結一個線程時不會保證線程資源的正常釋放, 通常是沒有給線程完成資源釋放工作的機會, 因此會導致程序可能工作在不確定的狀態下.

  • 使用 interrupt中斷方式.

線程安全的終止是其他線程通過調用某個線程的 interrupt() 方法對其進行中斷操作.
中斷好比對其他線程打了個招呼. 不代表線程 A 會立即停止自己的工作. 同樣的 A 線程也可以不理會這種中斷請求. 因爲java中的線程是協作式的, 不是搶佔式的. 線程內部可以通過調用isInterrupted()方法看返回是否爲true/false 來判斷是否被中斷, 也可以調用靜態方法 Thread.interrupted() 來進行判斷是否中斷.
interrupted()isInterrupted() 不同的是 interrupted()方法如果發現當前線程被中斷, 則會清除中斷標誌, 也就是如果第一次調用是true, 再次調用返回的就是false, 因爲之前的中斷狀態被清除了.

線程 A 調用了wait系列的函數, join方法或者 sleep 方法而被阻塞掛起, 這時候若是線程 B 調用線程 A 的interrupt() 方法, 線程 A 會在調用這些方法的地方拋出 InterruptedException 異常後會立即將線程 A 的中斷標誌位清楚, 即重新設置爲 false.

public static void main(String[] args)  {
  Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
          while (true){
              if(!Thread.currentThread().isInterrupted()){
                  System.out.println("thread name:" + Thread.currentThread().getName());
              }else {
                  return;
              }
          }

      }
  });
  thread.start();
  thread.interrupt();
  System.out.println("main is over");
}   

不建議自定義一個標誌位來終止線程的運行, 因爲run 方法裏有阻塞調用時會無法很快檢測到取消標誌, 線程必須從阻塞調用返回後, 纔會檢查這個取消標誌. 這種情況下使用中斷會更好. 因爲一般的阻塞方法, 如sleep() 等本身就支持中斷的檢查.

注意: 處於死鎖狀態的線程無法被中斷.

3. Join

將指定的線程加入到當前線程, 可以將兩個交替執行的線程合併爲順序執行, 比如在線程 B 中調用了線程 A 的 join() 方法, 那麼會直到線程 A 執行完畢後, 纔會繼續執行線程 B.

4. synchronized

關鍵字synchronized可以修飾方法或者以同步塊的形式來進行使用, 它主要確保多個線程在同一個時刻, 只能有一個線程處於方法或者同步塊中, 它爆炸了線程對變量訪問的可見性和排他性. 又稱爲內置鎖機制.

對象鎖是用於對象實例方法或者一個對象實例上的, 類鎖是用於類的靜態方法或者一個類的 class對象上的. 類的對象實例可以有很多個, 但是每個類只有一個 class 對象, 所以不同對象實例的對象鎖是互不干擾的, 但是每個類只有一個類鎖.

類鎖只是一個概念上的東西, 並不是真實存在的, 類鎖其實鎖的是每個類對應的 class 對象. 類鎖與對象鎖之間互不干擾.

5. 等待/通知機制

  • wait / notify / notifyAll

是指一個線程 A 調用了 wait() 方法進入到等待狀態, 而另外一個線程 B 調用了對 notify() 或者 notifyAll() 方法. 那麼線程 A 在接受到通知後就會被喚醒, 進而執行後續操作.

  • notify
    喚醒一個等待的線程, 喚醒的前提是該線程獲取到了鎖. 沒有獲得鎖就會重新進入 waiting 狀態.
  • nofifyAll
    喚醒所有等待狀態的線程.
  • wait
    調用該方法的線程將會進入到 waiting 狀態, 只有等待另外線程的喚醒或者被中斷纔會返回. 注意: 調用 wait() 方法後會釋放對象的鎖.

在調用 wait / notify / notifyAll 之前, 線程必須要獲得該對象的對象級鎖, 即只能在同步方法或同步塊中調用 wait / notify / notifyAll 方法. 進入wait() 方法後, 當前線程就會釋放鎖.

儘可能使用 notifyAll(), 因爲 notify() 只會喚醒一個線程, 我們無法確保被喚醒的這個線程一定就是我們需要喚醒的線程.

6. 死鎖

是指兩個或者兩個以上的進程在執行過程中, 由於競爭資源或者彼此通信而造成的一種阻塞的現象, 若無外力作用, 它們都將無法推進下去. 此時稱系統處於死鎖狀態.
死鎖是必然發生在多操作者(M>=2) 的情況下, 增多多個資源(N>=2, 並且 N<=M). 纔會發生這種情況.

例:
A 線程和 B 線程都想操作蘋果和香蕉這兩個對象, 但是線程 A 先拿到了蘋果, 線程 B 拿到了香蕉, 線程 A 就一直在等香蕉, 線程 B 就一直在等蘋果, 結果就產生了死鎖.

死鎖的危害

  • 線程不工作了, 但是整個進程又是活着的.
  • 沒有任何異常信息供我們檢查.
  • 一旦程序發生了死鎖, 是沒有任何辦法恢復的, 只有重啓程序.

死鎖的發生的四個必要條件

  • 互斥
    指線程對分配到的資源進行排他性使用. 即在一段時間內某資源只由一個線程佔用. 如果此時還有其他線程請求資源, 則請求者只能等待, 直至佔有資源的進程使用完畢釋放.

  • 請求和保持
    指線程已經保持至少一個資源, 但又提出了新的資源使用請求, 而該新的資源已經被其他線程佔有, 此時請求線程阻塞, 但是又對已持有的資源保持不釋放.

  • 不剝奪
    指線程已獲得的資源, 在未使用完之前, 不能被剝奪, 只能在使用完時由自己釋放.

  • 環路等待
    指在發生死鎖時, 必然存在一個線程資源的環形鏈, 即線程集合{T0, T1, T2...Tn}中的 T0 正在等待一個 T1 佔用的資源, 而 T1 呢又在等在 T2 佔用的資源, Tn又在等待 T0 佔用的資源.

死鎖的解除/預防/避免
理解了死鎖的原因, 尤其是產生死鎖的四個必要條件, 那麼只要打破這四個必要條件中的任何一個, 就能夠預防死鎖的發生.

例如

  • 當一個線程獨佔有一份資源後, 又申請了一個獨佔資源而無法滿足的情況下, 就退出原有的資源.
  • 採用資源預先分配策略, 即線程運行前申請全部資源, 滿足則運行, 不然就等待. 這樣就不會有佔有且申請的情況發生.
  • 實現資源有序分配策略, 對所有資源實現分類編號, 所有線程只能採用按序號遞增的形式申請資源.

7. 活鎖

兩個線程在嘗試獲得鎖的過程中, 發生線程之間的謙讓, 不斷髮生同一個線程總拿到同一把鎖, 在嘗試獲得另外一把鎖的時候因爲拿不到, 而將本來已持有的鎖釋放的過程.

解決方案

  • 每個線程休眠隨機數, 錯開拿鎖的時間.

8. 線程飢餓

低優先級的線程, 總拿不到執行時間.

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