java.lang.Thread

java.lang.Thread

位於java.lang包下的Thread是非常重要的線程類,它實現了Runnable接口。本篇文章圍繞Thread具體介紹一下Java線程的概念和Thread類中常用的方法

1.線程的狀態

線程從創建到最終的消亡, 要經歷若干個狀態。一般來說,線程包括以下幾個狀態:創建(new), 可運行(runnable), 阻塞(blocked), time waiting, waiting, 消亡(terminated)。
當需要建立起一個線程來執行某個子任務時,就創建了一個線程。但是線程創建之後,還沒有執行start()方法就不會開始執行. 另外因爲線程的運行需要一些條件(比如內存資源,程序計數器,Java虛擬機棧,這些都是線程私有的,要爲其分配一定的空間),只有程序運行需要的條件滿足了,才能進入可執行狀態。
當線程進入可執行狀態後,不代表立刻就能獲取CPU執行時間,也許CPU正在執行其他的線程,這時就需要等待。當得到CPU分配的執行時間後,線程才進入真正的運行狀態。
線程在運行過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程睡眠(睡眠一定時間後再重新執行),用戶主動讓線程等待,或者被同步塊阻塞,此時就對應着多個狀態:time waiting(睡眠或等待一定的時間),waiting(等待被喚醒),blocked(阻塞)。
線程遭到突然中斷或者子任務執行完畢,線程就會消亡。
選區_229.png

2.上下文切換

對於單核CPU來說,CPU在一個時刻只能運行一個線程,當在運行一個線程的過程中轉去運行另外一個線程,這個叫做線程上下文切換(進程也類似)。
由於可能當前線程的任務並沒有執行完畢,所以在切換時需要保存線程的運行狀態,以便下此重新切換回來時能夠繼續切換之前的狀態運行。因此就需要從用戶態轉入內核態,由內核態去保存線程執行的現場(寄存器狀態,程序計數器等數據)。
所以對於線程上下文的切換其實就是 存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。雖然多線程可以使得任務執行的效率得到提升。但是由於在線程切換時同樣會帶來一定的開銷代價,並且多個線程會導致系統資源佔用的增加,所以在進行多線程編程時要注意這些因素對效率的影響。

3.Thread類

選區_230.png
Thread類實現了Runnable接口, 在Thread類中, 有一些比較關鍵的屬性, 比如name是表示當前線程的名字,priority代表當前線程的優先級(最大爲10,最小爲1,默認爲5),daemon表示線程是否是守護線程,target表示要執行的任務.

Thread類中常用的方法

1>start()方法

start()方法用來啓動一個線程, 當調用start方法後, 系統纔會開啓一個新的線程來執行用戶定義的子任務.在這個過程中, 會爲相應的線程分配需要的資源.

2>run()方法

run方法不需要讓用戶去調用, 而是讓用戶在建立線程時進行重寫. 當通過start方法啓動一個線程之後, 當線程獲得了CPU執行時間, 就會進入run方法體去執行具體的任務.

3>sleep方法

sleep相當於讓線程睡眠, 交出CPU, 讓CPU去執行其他的任務
如果需要讓當前的線程暫停一段時間,並進入阻塞狀態,則可以通過Thread類的靜態sleep()方法來實現    
噹噹前線程調用sleep方法進入阻塞狀態後, 在其睡眠時間內, 該線程不會獲得執行機會. 即使系統中沒有其他可執行的線程, 處於sleep中的線程也不會執行, 因此sleep方法通常用來暫停程序的執行.
sleep方法不會釋放鎖

public class SleepTest {

    public static void main(String[] args) {

        Thread t1 = new Thread(new MyTest());
        Thread t2 = new Thread(new MyTest());
        t1.start();
        t2.start();
    }
}

class MyTest implements Runnable {
    @Override
    public void run() {
        synchronized (MyTest.class) {
            System.out.println("線程:" + Thread.currentThread().getName() + "啓動");
            try {
                System.out.println("線程" + Thread.currentThread().getName() +"開始睡眠");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程:" + Thread.currentThread().getName() + "繼續");
        }
    }
}

在上面的例子中, 我們開啓兩個線程, 每個線程在同步代碼塊中都執行了sleep方法. 根據打印結果可以看出, 在第一個線程執行sleep方法之後, 第二個線程並沒有進入同步代碼塊, 也就是說沒有獲取到鎖. 所以sleep方法並不會讓當前的線程釋放鎖.
選區_231.png

4>yield方法

yield方法與sleep方法有些類似, 也是Thread類提供的一個靜態方法, 也可以讓當前線程暫停. 但是yield方法不會阻塞當前線程, 它的作用是讓當前線程讓出自己的CPU執行時間, 轉入就緒狀態. 讓系統線程調度器重新調度一次. 所以完全有可能的是, 當某個線程調用了yield方法暫停之後, 線程調度器又將其調度出來重新執行.
與sleep方法相似, yield方法也不會釋放當前線程持有的鎖.

5>join方法

在main線程中, 調用thread.join方法, 則main線程會等待thread線程執行完畢或者等待一定的時間. 如果調用的是無參join方法, 則等待thread執行完畢, 如果調用的是指定了時間參數的join方法, 則等待一定時間.

public class JoinTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; ++i) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(i);
                }
            }
        });
        t1.start();
        try {
            t1.join();
            System.out.println("main結束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

選區_232.png

在上面的代碼中, main線程調用t1.join()方法之後, 在t1完成之後, 才繼續執行.
我們可以看一下join方法的源碼 :

選區_233.png
join方法的實現本質上還是依靠wait方法,所以join方法會釋放當前線程的鎖. 其實質就是不斷判斷thread線程是否還存活, 如果存活就阻塞調用該方法的線程, 直到thread線程運行結束.
方法join擁有使線程排隊的作用, 有些類似於同步的運行效果.

6>interrupt方法

interrupt就是中斷的意思. 單獨調用interrupt方法可以使得處於阻塞狀態的線程拋出一個異常, 也就是說可以用來中斷一個處於阻塞狀態的線程; 另外, 通過interrupt方法和isInterrupted()方法可以來停止一個正在運行的線程

public class InterruptTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("線程開始休眠");
                    Thread.sleep(10000);
                    System.out.println("線程休眠結束");
                } catch (InterruptedException e) {
                    System.out.println("線程被中斷");
                }
                System.out.println("線程執行結束");
            }
        });
        t1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執行中斷");
        t1.interrupt();

    }
}

上述線程在執行sleep方法時處於阻塞狀態, 此時在main線程中調用了t1.interrupt()方法. t1線程拋出異常, 被中斷
選區_234.png
但是interrupt方法並不能中斷正在運行的線程, 因爲調用interrupt方法相當於將中斷標誌位標記爲true, 可以在運行的線程中調用isInterrupted方法判斷中斷標誌是否被置位來中斷線程的執行. 另外 interrupted()方法可以檢測當前線程的interrupt狀態, 檢測完成後, 狀態清空

已經廢棄的方法

1>stop方法

stop方法是一個不安全的方法. 因爲調用stop方法會直接終止run方法的調用, 並且會拋出一個ThreadDeath錯誤, 如果線程持有哪個對象鎖的話, 會完全釋放鎖,導致對象狀態不一致.

2>destroy方法

已被廢棄

幾個關係到線程屬性的方法

1>getId

用來的到線程ID

2>getName和setName

用來得到或設置線程名字

3>getPriority和setPriority

用來獲取和設置線程優先級

4>setDaemon和isDaemon

用來設置線程是否成爲守護線程和判斷線程是否是守護線程.

守護線程和用戶線程的卻別是 : 守護線程依賴與創建它的線程, 而用戶線程不依賴. 例如如果在main線程中創建了一個守護線程, 當main方法執行完畢之後, 守護線程也會隨之消亡. 而用戶線程則不會, 用戶線程會一直運行到其運行完畢. 在JVM中,像垃圾收集器線程就是守護線程.

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