目錄
一、Thread 的常?構造?法
繼承Thread代碼:
實現Runnable接口代碼:
二、Thread 的?個常?屬性
1、id:
2、獲取線程的名字。
3、進程的狀態:
4、在java中設置的優先級,
5、是否后臺線程,
6、是否存活,
7、是否中斷,
8、等待線程(結束),
9、獲取線程的引用,currentThread()
一、Thread 的常?構造?法
最后一個線程組的概念是java的概念,和系統中的線程組不一樣,不是同一個東西。
第一個和第二個構造方法在初階一有介紹,
鏈接:多線程Thread(初階一:認識線程)-CSDN博客
第三是創建一個線程,并且可以給這個線程命名,并不是它默認的Thread-0 / 1 / 2.....,第四個也是可以給線程對象命名,不過是使用接口的方法。
繼承Thread代碼:
class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello thread1");}String name;public MyThread(String name) {super(name);this.name = name;} } public class ThreadDemo1 {public static void main(String[] args) {Thread t1 = new MyThread("這是我的線程");t1.start();Thread t2 = new Thread("這是我的線程"){@Overridepublic void run() {System.out.println("hello thread2");}};t2.start();} }
執行效果:
兩個線程:
上面兩種的兩種創建線程方式都可以。
實現Runnable接口代碼:
class MyThread2 extends Thread {String name;public MyThread2(String name) {super(name);this.name = name;}@Overridepublic void run() {System.out.println("hello thread1");} } public class TestDemo2 {public static void main(String[] args) {Thread t1 = new MyThread2("這是我的線程1");t1.start();Thread t2 = new Thread("這是我的進程2") {@Overridepublic void run() {System.out.println("hello thread2");}};t2.start();} }
執行效果:
兩個線程:
注意:我們創建的線程如果不起名字,默認是Thread-0 1 2 3....,給不同線程起不同名字,對于線程的執行,沒啥影響,主要是為了方便調試;線程之間的名字是可以重復的,在同一個工作中,需要多個線程完成,都可以起一樣的名字;但是名字也不要亂起,最后要有一定的描述性。
二、Thread 的?個常?屬性
1、id:
jvm自動分配的身份標識,會保證唯一性。
2、獲取線程的名字。
3、進程的狀態:
就緒狀態、阻塞狀態;線程也有狀態,Java中對線程的狀態進行了進一步的區分(比系統原生的狀態更豐富一些)。
4、在java中設置的優先級,
效果不是很明顯,因為系統是隨機調度的(對內核的調度器調度過程會產生一些影響)。
5、是否后臺線程,
也稱為是否守護線程(比較抽象),所以記住和理解是否后臺線程會輕松一些,與此相反,也有前臺線程(和Android上的前臺app,后臺app完全不同)。
后臺線程和前臺線程的區別:前臺線程的運行,會阻止進程結束
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?后臺線程的運行,不會阻止進程結束
我們創建的線程,默認是前臺進程。
如下代碼:
public class TestDemo3 {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) {throw new RuntimeException(e);}}}};t.start();} }
執行效果:會不停的輸出打印hello thread
那我們試著把這個線程設置為后臺線程試試(默認為前臺),代碼:
public class TestDemo3 {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) {throw new RuntimeException(e);}}}};//在start之前,設置線程為后臺線程(不能在start之后設置)t.setDaemon(true);t.start();} }
執行效果:
根本就沒有打印hello thread,進程就結束了,因為是后臺進程,不會阻止進程的結束。
設為true是后臺,后臺,可以理解為背后,不出面的人,你感知不到;后臺不會阻止進程結束。
不設為true是前臺,前臺,可以理解為明面上的人,你能感知到;前臺會阻止進程結束。
6、是否存活,
isAlive( )表示內核中的線程(PCB)是否還存在。java代碼中定義的線程對象(Thread)實現,雖然是表示一個線程,但這個對象本身的生命周期,和內核中的PCB生命周期? ,是不完全一樣的。
isAlive的測試,代碼:
public class TestDemo4 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println("start之前:" + t.isAlive());t.start();System.out.println("start之后" + t.isAlive());Thread.sleep(2000);System.out.println("t結束之后" + t.isAlive());} }
執行效果:
解釋:啟動線程之前,也就是start之前,t這個實例線程是還沒開始的,所以isAlive返回的是false,start后就查看這個線程存不存在,因為線程也剛啟動,所以isAlive返回的是true,休眠兩秒之后,t線程已經跑完了,所以isAlive返回的也是false。
7、是否中斷,
也是終止的意思。
下面寫一個代碼,用boolean類型的變量來控制while循環,也起到終止線程的作用。
public class ThreadDemo1 {private static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!isQuit) {System.out.println("我是一個線程,正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("線程工作完畢");});t.start();Thread.sleep(3000);System.out.println("讓線程結束");isQuit = true;} }
注意:這里的isQuit變量放在類中,不放在方法里作為局部變量的原因是涉及到了變量捕獲,如果放在main方法里,是局部變量,就必須要加 final 修飾將其變成常量,為什么呢?這時因為main線程和我們創建的線程的棧幀生命周期不同,如果main線程先結束了,我們創建的線程要獲取這個變量,但是我們main線程的棧幀生命周期已經結束了,我們拿不到這個變量,java這里的做法就比較暴力,直接把這個變量變成常量,要么這個變量就是全局變量,以至于不會發生上面的情況。
執行效果:
這時,我們也可以使用jdk里自帶的方法:isInterrupted( )
代碼如下:
public class ThreadDemo2 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("我是一個線程,正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("線程執行完畢");});t.start();Thread.sleep(3000);System.out.println("讓線程結束");t.interrupt();} }
執行效果:
這個線程并不會結束,會繼續執行。
解釋:可以看到這里拋出了個異常,而這個異常是InterruptedException 異常
如果沒有sleep,interrupt是可以讓線程順利結束的,但是有了sleep就會引起變數。
這說明,sleep這出現問題了,為什么呢?原因是這個線程還在執行,main線程休眠3秒后,終止了這個線程,但sleep還沒休眠夠1秒,這里就會提前喚醒。
這里,提前喚醒,會做兩件事:
1、拋出?InterruptedException (緊接著就會被catch獲取到)。
2、清除?Thread 對象的 isInterrupt 標志位。(sleep清空標志位,是為了給程序猿更多的“可操作性空間”)。
所以,interrupt已經把標志位設置為true了,sleep被提前喚醒,清除了isInterrupt標志位后,就又把標志位設回了false,這個while循環還是能進去的,所以線程還在繼續執行。
此時,程序猿就可以在catch語句中加入一些代碼,來做一些處理:
1、讓線程立即結束。(加上break)
2、讓線程不結束,繼續執行。(不加break)
3、讓線程執行一些邏輯后,再結束。(寫一些其他代碼,再加break)
對于一個服務器程序來說,穩定性很重要,這些所謂的“問題”,在java中就會以異常的形式體現出來,可以通過catch語句,對這些異常進行處理。主要的幾種處理方式:
1、嘗試自動恢復
2、記錄日志
3、發出警報
8、等待線程(結束),
join( )。我們知道,多線程的執行順序是不同的(隨機調度,搶占式執行),有不可預期性,雖然線程的調度是無序的,但是我們可以調用一些api,來影響帶線程的執行順序,join就是一種方式,影響 線程結束 的先后順序。比如:t2 線程等待 t1,這時,一定是 t1先結束,t2 后結束。這里的join就會使 t2 線程阻塞。
代碼:
public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("我是一個線程,正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("線程結束");});t.start();//t.join();System.out.println("這時一個主線程,在t線程結束后再執行");} }
我們想讓t線程執行完后再執行主線程,如果不寫join方法。因為多線程的調度是隨機性的,執行效果如下:
執行效果和我們預期的不一樣,加上join方法后,
代碼:
public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("我是一個線程,正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("線程結束");});t.start();t.join();System.out.println("這時一個主線程,在t線程結束后再執行");} }
執行效果:
結果和我們想要預期效果相同。這里的join的意思是:讓 t 線程執行完,再執行本線程。
這里是讓main線程主動放棄去調度器調度,t 線程執行完后,main線程才執行,這也代表哪個線程調用join,哪個線程就是阻塞等待。這里就有了先后順序。
注意:這里的先后順序和優先級不同,優先級是系統調度器,在內核中完成的工作,即使優先級有差異,但是每個線程的執行順序還是隨機的(控制不了)。
等待線程結束還有兩個重載方法,里面放的是要等待的時間,如圖:
如果join不加參數,就是死等,第一個加參數的就是:等待多少毫秒,第二個加參數:數值精確到納秒,不過很少用,因為計算機精確不到這么小。加參數是帶有時間的等。
注意,有時候不想等,我們也可以不等,例如加 interrupt,可以終止這個等待,我們也可以在idea看到join的實現,是throws了?InterruptedException的。
9、獲取線程的引用,currentThread()
我們對于一個類繼承Thread是可以通過this拿到線程的實例的,
代碼如下:
class MyThread extends Thread {@Overridepublic void run() {System.out.println("id:"+ this.getId() + " name:" + this.getName());} } public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();} }
效果如下:
但是,如果是接口或者我們使用匿名內部類 / lambda表達式,就不能獲取到Thread的引用了,這時,Thread已經給我們提供了方法獲取引用了:Thread.currentThread()。如下展示:
代碼:
public class ThreadDemo2 {public static void main(String[] args) {Thread t1 = new Thread(() -> {Thread t = Thread.currentThread();System.out.println(t.getId());});Thread t2 = new Thread(() -> {Thread t = Thread.currentThread();System.out.println(t.getName());});t1.start();t2.start();} }
效果如下: