Java線程狀態及同步鎖

線程的生命歷程

線程的五大狀態

  • 創建狀態:簡而言之,當創建線程對象的代碼出現的時候,此時線程就進入瞭創建狀態。這時候的線程隻是行代碼而已。隻有調用線程的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){
        ....
    }
}

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

到此這篇關於Java線程狀態及同步鎖的文章就介紹到這瞭,更多相關Java線程狀態及同步鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: