首頁 > 軟體

Java執行緒中的常見方法(start方法和run方法)

2022-07-29 22:01:23

start方法和run方法

$start()$方法用來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到$cpu$時間片,就開始執行$run()$方法。而直接呼叫$run()$方法,僅僅只是呼叫了一個類裡的方法,其本質上還是在當前執行緒中執行的,因此只有使用$start()$方法來呼叫$run()$方法才能實現真正的多執行緒。

範例程式碼

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.run();
    }
}

上述程式碼是直接呼叫的$run()$方法。可以看到列印資訊裡,是$main$執行緒執行了這個方法。

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.start();
    }
}

而如果使用$start()$方法啟動,才是真正的由$t1$執行緒執行的$run$方法。

注意

需要注意的是,當$Thread$物件呼叫了$start()$方法後,就會進入就緒狀態,處於就緒狀態時無法再呼叫$start()$方法,否則就會丟擲$IllegalThreadStateException$異常,如下程式碼所示

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.start();
        t1.start();
    }
}

異常資訊:

sleep方法與yield方法

sleep

  • 呼叫$sleep()$方法會讓當前執行緒從$Running$狀態變成$Time Waiting$狀態(阻塞)
  • 其它執行緒可以使用$interrupt$方法打斷正在睡眠的執行緒,此時$sleep$方法會丟擲InterruptedException
  • 睡眠結束後的執行緒未必會立刻得到執行
  • 建議用$TimeUnit$的$sleep$代替$Thread$的$sleep$來獲得更好的可讀性範例程式碼
@Slf4j(topic = "c.Test5")
public class Test5 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        log.debug("t1 state {}", t1.getState());
        //讓主執行緒休眠500ms
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
    }
}
//17:13:21.729 [main] DEBUG c.Test5 - t1 state RUNNABLE
//17:13:22.245 [main] DEBUG c.Test5 - t1 state TIMED_WAITING

上述程式碼中,首先啟動$t1$執行緒,此時列印執行緒的狀態應該是處於$RUNNABLE$狀態,而讓主執行緒休眠是防止主執行緒先執行列印,但是還未進入到$sleep()$狀態。當執行到$run()$裡邊的$sleep$方法時,執行緒進入$TIMED WAITING$狀態

@Slf4j(topic = "c.Test6")
public class Thread6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("enter sleep");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt t1");
        //被喚醒
        t1.interrupt();
    }
}

執行結果

上述程式碼中,當$start$方法啟動後,$t1$執行緒進入睡眠狀態,列印提示資訊,睡眠時間為$2s$,在$main$執行緒中睡眠$1s$後打斷$t1$執行緒的睡眠,提示打斷資訊,並且呼叫$interrupt()$方法,此時執行緒被打斷,丟擲異常。

$TimeUnit$類中新增了以什麼單位去睡眠,可讀性更好,但是本質上沒區別,只是進行了單位換算

TimeUnit.SECONDS.sleep(1);//該語句作用是睡眠一秒

yield

呼叫$yield$會讓當前程序從$Running$進入到$Runnable$就緒狀態,然後排程執行其他執行緒具體的實現依賴於作業系統的任務排程器,(即當任務排程器中沒有其他任務時,即使讓出$cpu$,也會繼續執行該執行緒)$sleep$執行後是進入阻塞狀態,此時睡眠時間不結束,就不會分配$cpu$給該執行緒,但是$yield$是進入就緒狀態,即如果沒有其他執行緒需要執行,那麼還會給該執行緒分配時間片,這是$sleep$和$yield$的最大區別執行緒優先順序

執行緒優先順序

會提示排程器優先排程該執行緒,但它僅僅是一個提示,排程器可以忽略他
如果$cpu$比較忙,那麼優先順序高的會獲得更多的時間片,可$cpu$空閒時,優先順序幾乎沒有

sleep的應用-防止cpu佔用100%

在沒有利用$cpu$來計算時,不要讓$while(true)$空轉浪費$cpu$,這時可以可以使用$yield$或者$sleep$來讓$cpu$的使用權交給其他程式

while (true) {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
  }
}

可以使用$wait$或者條件變數達到類似的效果
不同的是後兩者都需要加鎖,並且需要相應的喚醒操作,一般適用於要進行同步的場景
$sleep$適用於無需鎖同步的場景

join方法

以下程式的列印結果:

@Slf4j(topic = "c.Test6")
public class Test6 {
    static int r = 0;
    public static void main(String[] args) {
        test();
    }
    private static void test() {
        log.debug("開始");
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                log.debug("開始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("結束");
                r = 10;
            }
        };
        t.start();
        log.debug("r的值是{}", r);
        log.debug("結束");
    }
}

因為主執行緒和$t1$執行緒是並行的,$t1$執行緒需要$1s$後才能計算出$r$的值,而主執行緒一開始就要列印出$r$的值,因此列印的值為0

解決方法:

在$t.start();$後邊加上$t.join();$即可。$join$的作用是等待某執行緒執行結束。
以呼叫方的角度來說,需要等待結果返回才能繼續執行就是同步,不需要等待返回結果就能繼續執行的就是非同步。

因此$join$方法實際上是讓其同步執行

有實效的等待

$join(毫秒)$方法裡可以有一個引數是傳入等待的時間,如果執行緒執行時間大於等待時間,則等待時間到了之後,就會停止等待。如果執行緒執行時間小於等待時間,則執行緒執行完畢之後,等待也會跟著結束。不會把設定的等待時間過完。

interrupt方法

打斷$sleep, wait, join$的執行緒,即打斷阻塞狀態的執行緒
打斷$sleep$的執行緒,會清空打斷狀態

@Slf4j(topic = "c.Test7")
public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                log.debug("sleep...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
        log.debug("打斷標記: {}", t.isInterrupted());
    }
}

打斷正常執行的執行緒,不會清空打斷狀態

因此我們可以線上程中判斷打斷標記,來決定是否被打斷,以及執行被打斷之前的收尾工作。

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("執行緒被打斷了");
                        break;
                    }
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
    }
}

守護執行緒

預設情況下,$java$需要等待所有執行緒都執行結束,才會結束。有一種特殊的執行緒叫做守護執行緒,只要其他非守護執行緒執行結束了,即使守護執行緒的程式碼沒有執行完畢,也會強制結束。

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                while (true) {

                }
            }
        };
        //設定執行緒為守護執行緒
        t.setDaemon(true);
        t.start();
        Thread.sleep(1000);
        log.debug("主執行緒結束");
    }
}

如果不把$t$設定為守護執行緒,則因為執行緒內部的死迴圈,導致程式不會結束執行。

到此這篇關於Java執行緒中的常見方法(start方法和run方法)的文章就介紹到這了,更多相關Java執行緒方法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com