Java線程中的常見方法(start方法和run方法)

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線程方法內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: