Java執行緒狀態及同步鎖

Thales_ZeeWay發表於2021-11-12

執行緒的生命歷程

執行緒的五大狀態

  • 建立狀態:簡而言之,當建立執行緒物件的程式碼出現的時候,此時執行緒就進入了建立狀態。這時候的執行緒只是行程式碼而已。只有呼叫執行緒的start()方法時,執行緒的狀態才會改變,進入就緒狀態
  • 就緒狀態:在這個狀態下的執行緒,已經做好了隨時執行的準備,但是並不意味著會立刻開始執行。還需要等待CPU的隨機排程,隨機執行只有當執行緒被CPU排程執行成功,此時的執行緒才算是進入下一個狀態——執行狀態。
  • 執行狀態:執行緒處於執行狀態,主要是在執行執行緒中的程式碼塊。
  • 阻塞狀態:線上程執行過程中,當執行緒程式碼塊中呼叫了執行緒的sleep(),yield(),同步鎖定或者其他使執行緒阻塞的方法,此時的執行緒無法繼續執行下去,進入了阻塞狀態(執行緒程式碼塊的自身邏輯混亂也可以使執行緒阻塞)。當造成執行緒阻塞的阻塞事件解決之後,執行緒不會回到執行狀態,而是回到就緒狀態,再次等待CPU的排程執行。需要注意的是,阻塞並不意味著執行緒執行終止
  • 死亡狀態當執行緒成功執行完所有的程式碼之後,執行緒就結束了,也進入了死亡狀態。執行緒一旦死亡,就無法再次啟動,注意這裡和阻塞狀態的不同。同樣的,當執行緒執行一半的時候被強行結束終止,也算進入死亡狀態,也無法被再次啟動。

執行緒的方法

Java中的thread類自帶有執行緒的一些方法,這些方法可以讓執行緒睡眠,插隊,提高執行緒排程的優先順序等等,它們提供了改變執行緒狀態的操作手段。(不過在JDK幫助文件中,一些方法已經不推薦使用)

執行緒方法中的一些有趣的地方

  • 執行緒睡眠是以毫秒為單位的。一秒等於一千毫秒。一般在測試程式中呼叫睡眠方法,是為了提高程式問題的發生性,或者說為了發現bug

  • 執行緒停止,由於Java中自帶的停止方法不太好用,所以一般都是自己寫一個停止的方法,標定一個布林型別的Flag作為執行緒執行的標誌,當flag為真時執行緒執行,當flag為假時執行緒停止。

  • 執行緒禮讓是將正在執行的執行緒暫停回到就緒狀態,而不是變為阻塞狀態。有趣的是禮讓不是一定會成功的,因為執行緒由就緒狀態進入執行狀態是由CPU隨機排程的。所以禮讓的執行緒有可能在下次的排程中再次提前排程,提前執行。

  • 執行緒插隊(join方法),強制阻塞其他執行緒,只有插入的執行緒執行完成之後,其他執行緒才能繼續執行

  • 執行緒雖然有優先順序的區別(1-10),但是在實際執行中還是得看CPU的心情排程執行,優先順序高只是被排程的概率高一點。Java中自帶有執行緒優先順序的檢視和改變方法(執行緒的優先順序設定最好線上程啟動之前)

    public class ttp {
        public static void main(String[] args) {
            //主執行緒的預設優先順序
            System.out.println(Thread.currentThread().getName()+"--->" + Thread.currentThread().getPriority());
            MyPriorty mm = new MyPriorty();
            Thread t1 = new Thread(mm);
            Thread t2 = new Thread(mm);
            Thread t3 = new Thread(mm);
    
    
            
            t1.setPriority(10);
            t1.start();
    
            t2.setPriority(4);
            t2.start();
    
            t3.setPriority(6);
            t3.start();
    
    
        }
    }
    
    //Runnable介面實現介面,run方法為列印執行緒名稱和執行緒的優先順序
    class MyPriorty implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"--->" + Thread.currentThread().getPriority());
        }
    }
    //這裡的輸出有多種結果,因為優先順序只是增加了執行緒被排程執行的機率
    
  • 使用者執行緒和守護執行緒。守護執行緒的作用是保證使用者執行緒的執行過程正常,例如Java中的記憶體回收執行緒和後臺記錄操作日誌等等,這些都是守護執行緒。虛擬機器必須等待使用者執行緒執行完畢,不用等待守護執行緒執行完畢。當使用者執行緒完成後,虛擬機器就自動關閉,守護執行緒也就自動死亡了。

    //Java的Thread類自帶設定守護執行緒的方法
    Thread.setdaemon(true) //設定為守護執行緒
    //一般我們建立的執行緒預設都是使用者執行緒
    
  • 執行緒同步。執行緒同步是出現多個執行緒訪問同一個物件並都想對其進行操作的時候必須考慮的問題。不進行執行緒同步(併發)控制的多執行緒是不安全的。

    
    //執行緒不安全,出現了-1張票以及有兩個執行緒拿到同一張票的錯誤,所以這是一個不安全的執行緒
    public class test05 {
    
        public static void main(String[] args) {
            buyTicket b1 = new buyTicket();
            Thread t1 = new Thread(b1,"you");
            Thread t2 = new Thread(b1,"i");
            Thread t3 = new Thread(b1,"he");
            Thread t4 = new Thread(b1,"she");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
    
        }
    }
    
    
    class buyTicket implements Runnable{
        //剩餘票數
        private int ticketNums = 12;
        private boolean flag = true;
    
        @Override
        public void run() {
            while(flag){
                buy();
            }
        }
    
        private void buy(){
            if(ticketNums <= 0){
                flag = false;
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "買到了第" + ticketNums-- + "張票");
        }
    }
    

執行緒同步

執行緒同步實質上是一個等待機制。執行緒同步時會將多個執行緒放入物件等待池中進行排隊(佇列),等待前一個執行緒執行操作完畢,再有下一個執行緒進行執行操作。每個物件都有一個獨有的鎖(排他鎖),每個執行緒執行時都會獲得物件的排他鎖,這樣只有獲得鎖的執行緒可以對物件進行操作,執行結束後排他鎖被下一個執行緒獲得。總結來說,執行緒同步的形成條件就是:佇列+鎖

在訪問時加入鎖機制synchronized,當一個執行緒獲得物件得排他鎖,獨佔資源,其他執行緒必須等待,使用後釋放鎖即可

執行緒同步也有一些存在的問題(大部分是以犧牲效能以保證安全)

  • 一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起
  • 在多執行緒競爭下,加鎖,釋放鎖會導致較多的上下文切換和排程延時,引起效能問題
  • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖,會導致優先順序倒置,引起效能問題

一般來說,synchronized是方法宣告中新增,預設對方法中的this物件資源新增鎖。如果要對其他共享資源物件進行鎖定,則要使用同步監視器

  • 一號執行緒訪問,鎖定監視器,開始執行中間的程式碼
  • 二號執行緒訪問,發現監視器被鎖,無法訪問,掛起
  • 一號執行緒執行完畢,解鎖監視器
  • 二號執行緒訪問,監視器無鎖,鎖定並執行程式碼
public void xxx(){
    //其中ob就是想要鎖住的任意的共享資源物件
    //程式碼塊是放在同步監視器中的
    synchronized(obj){
        ....
    }
}

需要注意的是,這樣的鎖理論上是可行的 ,但是在實際執行中雖然加了鎖,但是還是有可能出現不安全的現象

"本站所有文章均為原創,歡迎轉載,請註明文章出處:Thales_ZeeWay百度和各類採集站皆不可信,搜尋請謹慎鑑別。技術類文章一般都有時效性,本人習慣不定期對自己的博文進行修正和更新,因此請訪問出處以檢視本文的最新版本。"

作者:Thales_ZeeWay
連結:Java執行緒狀態及同步鎖
來源:部落格園

著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章