這樣的代碼,雖然也能打印hello thread,但是沒有創建新的線程,而是直接在main方法所在的主線程中執行了run的邏輯
start方法,是調用系統api,真正在操作系統內部創建一個線程。這個新的線程會以run作為入口方法(執行run)的邏輯,run方法,不需要在代碼中顯式調用
此處遇見受查異常為什么不能用throws呢?
因為,如果給run方法聲明加上throws,此時就意味著和父類Thread的run的方法聲明不一致,就無法構成方法重寫了。
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 創建并啟動MyThread線程MyThread myThread = new MyThread();myThread.start();// myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
輸出:
但是如果換成這樣的代碼:
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 創建并啟動MyThread線程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
輸出:
由此可見run方法并沒有創建一個新的進程,從始至終只有一個主線程在運行,只有將run的方法執行完才能去打印hello main 但是由于這里的 run方法是一個死循環,所以一直沒有打印hello main
通過觀察我們可以發現這里的main和thread的打印,并不是嚴格交替的。由于兩個打印,都加了sleep(1000)。當1000ms時間到的時候,這兩個線程哪一個限制性,這個順序是不確定的。
(操作系統調度線程的順序是無需,不可預測的,隨機的 )
可以通過Java jdk中的jconsole工具來觀察進程
我的jdk-jconsole工具目錄:C:\Program Files\Java\jdk-17\bin
折線圖
這張圖列出了當前進程中所有的線程。剩下的線程,都是JVM自帶的,這些線程進行了一些背后的操作,比如負責垃圾回收,記錄統計信息,記錄一些調試信息。
如果將Thread.start()改成Thread.run()——>
我們會發現剛剛的Thread0沒有了
一、Java中創建線程的方法
(1)創建子類,繼承Thread,重寫run,調用start
package Thread;class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
public class Demo1 {
public static void main(String[] args) {System.out.println("你好!");// 創建并啟動MyThread線程MyThread myThread = new MyThread();// myThread.start();myThread.run();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
}
(2)創建子類,實現Runnable,重寫run,搭配Thread對象進行start
package Thread;class MyRunnable implements Runnable {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}public class Demo2 {public static void main(String[] args) {Runnable runnable = new MyRunnable();//runnable沒有start方法,所以不能直接調用run方法,需要將runnable對象作為參數傳遞給Thread類的構造方法,然后調用start方法啟動線程 Thread thread = new Thread(runnable);thread.start();}
}
使用Runnable描述線程要執行的任務是啥,真正去創建線程的工作,還是由Thread來負責。
interface是接口,不包含具體的方法的實現,只是提供一系列抽象方法,讓子類去重寫
*接口的的default一般不用,日常開發的時候,用到接口,都是希望全都提供抽象方法。除非是接口提供了一組方法,這一組方法中存在一些“公共的邏輯”,就可以在接口中搞default方法,使得這個方法表示公共邏輯,后面就可以在重寫其他抽象方法的時候去調用了。
對于第一種寫法(繼承Thread)描述任務的時候,任務代碼是寫到Thread子類中的,意味著任務內容和Thread類的耦合程度更高(*寫代碼要高內聚/低耦合)
未來如果想把這個任務給別的“主體”去執行(執行一個任務,線程只是其中一個選項,進程也可以,協程亦可以...)
第二種寫法,任務是寫到Runnable中的,幾乎不涉及到任何和“線程”相關的概念,任務內容和Thread概念的耦合是很小的,幾乎沒有
任務內容和Thread概念的耦合是很小的,幾乎沒有
后序就可以把這個任務交給進程、協程來執行
(*協程:近幾年提出的概念,可以理解為“輕量級線程”)
(3)繼承Thread,重寫run,調用start 通過匿名內部類
package Thread;public class Demo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};t.start();}}
此處就是創建了沒有名字的匿名內部類,這個類就是Thread的子類,子類重寫了run方法,同時也創建了子類的實例,通過t引用指向。
(4)實現Runnable,重寫run,搭配Thread調用start 通過匿名內部類
package Thread;public class Demo4 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};Thread t = new Thread(runnable);t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
(5)更簡單的寫法,基于lambda表達式創建線程
lambda表達式,本質上是“匿名方法”
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
二、后臺線程與前臺線程
后臺線程:線程沒運行完,進程可以結束(線程不能夠組織進程結束)
前臺線程:線程沒有運行完,進程就不會結束(線程能夠組織進程結束)
main線程和我們自己創建的線程都是前臺線程,isDaemon = false;
剩下的線程就是后臺線程,isDaemon = true;(守護線程)
通過setDaemon設置線程為后臺(*必須在start之前set)
什么樣子的線程是前臺線程,什么樣子的線程是后臺線程呢?
如果一個線程做的任務很重要,這個任務必須要昨晚你,就應該設置這個線程為前臺線程
如果一個線程做的任務無關緊要/周期性無期限執行(比如說JVM的垃圾回收線程),就應該設置為后臺線程
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});t.setDaemon(true);t.start();//把t線程設置為守護線程,當main線程結束時,t線程也會結束for(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
三、判斷線程是否存活
package Thread;public class Demo5 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}});System.out.println("t線程是否存活:"+t.isAlive());//false,t線程還沒有啟動,所以isAlive返回falset.setDaemon(true);t.start();//把t線程設置為守護線程,當main線程結束時,t線程也會結束System.out.println("t線程是否存活:"+t.isAlive());//true,t線程已經啟動,所以isAlive返回truefor(int i = 0;i<3;i++){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("t線程是否存活:"+t.isAlive());//false,t線程已經結束,所以isAlive返回false}}