Java中Thread類的使用和它的屬性
在java中可以進行多線程編程,在java標準庫中提供了一個Thread類,來表示線程操作。Thread類可以視為java標準庫提供的一組解決多線程編程的一組API.
創(chuàng)建好的Thread實例,和操作系統(tǒng)中的線程是一一對應(yīng)的。操作系統(tǒng)提供了關(guān)于線程的API(C語言風(fēng)格),java在對其進行封裝就成Thread類。
創(chuàng)建線程
方法一:繼承Thread類
class MyThread extends Thread{ @Override public void run() { //此時只是定義處理一個線程類,在系統(tǒng)中總是還沒有創(chuàng)建出 新的線程。 System.out.println("hello thread"); } } public class TestDemo11 { public static void main(String[] args) { //創(chuàng)建線程 Thread t = new MyThread(); //啟動線程,在系統(tǒng)中創(chuàng)建出了新的線程 t.start(); } }
線程之間是并發(fā)執(zhí)行的
class MyThread3 extends Thread{ @Override public void run() { //定義一個線程類 while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class TestDemo13 { public static void main(String[] args) { Thread t = new MyThread3(); t.start();//啟動t線程 while(true){ System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
我們可以看到1s當(dāng)執(zhí)行一個線程中的代碼之后
進入阻塞狀態(tài),那么下一秒要喚醒那個線程呢?
我們可以看到執(zhí)行出來的兩個線程中打印出來的日志的順序是不確定的。每一輪,1s之后,到底是喚醒thread線程還是喚醒main線程,這是不確定的。(搶占式執(zhí)行),對于操作系統(tǒng)來說,從宏觀上對線程進行調(diào)度的順序是隨機的
此處在說明一下Thread.sleep()方法,sleep()這個方法到了ms級別沒有那么精確。當(dāng)調(diào)用這個方法之后,把線程強制處于中阻塞(睡眠狀態(tài)),但是當(dāng)阻塞時間結(jié)束之后,并不是立即在cup上繼續(xù)執(zhí)行該線程,如果Thread.sleep(1000),當(dāng)通過1s后,阻塞時間結(jié)束,但是在1001ms,線程也許不會立即執(zhí)行。也許操作系統(tǒng)中的cup在忙別的線程?;蛟S該線程在1006ms才執(zhí)行。
方法二:實現(xiàn)Runnable接口中的run()方法
//Runnable其實就是描述一個任務(wù) //創(chuàng)建一個類,實現(xiàn)Runnable接口,重寫Runnable類中的run方法,在run()方法中,描述的是該線程要指向的哪些任務(wù)。 class MyThread2 implements Runnable{ @Override public void run() { System.out.println("hello thread"); } } public class TestDemo12 { public static void main(String[] args) { //創(chuàng)建線程,把創(chuàng)建好的Runnable實例傳給Thread實例 Thread t = new Thread(new MyThread2()); t.start(); } }
方法三:利用內(nèi)部類
方法三其實是方法一個的翻版,就是把上面的兩種代碼,改成了匿名內(nèi)部類。
public class TestDemo14 { public static void main(String[] args) { Thread t1 = new MyThread(){ @Override public void run() { System.out.println("hello thread1"); } }; Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("hello thread2"); } }); t1.start(); t2.start(); } }
方法四:使用lambmd表達式
public class TestDemo15 { public static void main(String[] args) { //其實就是使用lambad表達式代替Runnable Thread t1 = new Thread(()->{ //()表示無參的run()方法 System.out.println("hello thread1"); }); } }
使用線程的好處
為了更方便的體現(xiàn)出多線程的好處,在這里我們從0開始自增1,一直自增到10_0000_0000,使用串行方法,和并行方法,并且獲取他們執(zhí)行代碼的時間,進行比較。
public class TestDemo16 { public static void func1() throws InterruptedException { long big = System.currentTimeMillis(); //串行執(zhí)行 Thread t = new Thread(()->{ long a = 0; for(long i = 0;i<10_0000_0000;i++){ a++; } }); t.start(); t.join(); long end = System.currentTimeMillis(); System.out.println("串行消耗時間:" + (end - big) + "ms"); } public static void func2() throws InterruptedException { long big = System.currentTimeMillis(); Thread t1 = new Thread(()->{ long b = 0; for(long i = 0;i< 10_0000_0000 / 2;i++){ b++; } }); t1.start(); Thread t2 = new Thread(()->{ long c = 0; for(long i = 0;i<10_0000_0000/ 2;i++){ c++; } }); t2.start(); t1.join(); t2.join(); long end = System.currentTimeMillis(); System.out.println("并行執(zhí)行消耗時間:" + (end - big) + "ms"); } public static void main(String[] args) throws InterruptedException { func1();//串行執(zhí)行 func2();//并行執(zhí)行 } }
我們可以很明顯的看出串行時間要比并行時間長的多,串行時間幾乎是并行時間的2倍。
Thread類的其他屬性和方法
Thread的常見構(gòu)造方法
屬性 | 獲取方法 |
---|---|
ID | getId() |
名稱 | getName() |
狀態(tài) | getState() |
優(yōu)先級 | getPriority() |
是否后臺線程 | isDaemon() |
線程是否存活 | isAlive() |
線程是否被中斷 | isinterrupted() |
- ID是線程唯一的標識,不同的線程之間不會重復(fù)
- 名稱是各種調(diào)試工具用到的
- 狀態(tài)標識當(dāng)前線程的一種情況
- 優(yōu)先級高的線程,理論上更容易被執(zhí)行到
- 是否存活簡單理解為run()方法是否執(zhí)行結(jié)束
給一個線程起名字
Thread(String name)
這個東西是給thread對象起一個名字,具體起什么名字和線程的執(zhí)行效率無關(guān),起名字主要依靠于程序員,方便程序員在后期進行調(diào)試。
public class TestDemo17 { public static void main(String[] args) { //給線程器名字 Thread t1 = new Thread(()->{ while(true) { System.out.println("hello thread1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },"thread1"); Thread t2 = new Thread(()->{ while(true) { System.out.println("hello thread2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },"thread2"); t1.start(); t2.start(); } }
那么怎樣才能看到,我們定義好的線程名字呢?
注意:當(dāng)我們要查看線程名字的時候,程序必須要正在執(zhí)行,否則我們查找不到對應(yīng)的線程名字。
判斷一個線程是否存活
簡單的說就是操作系統(tǒng)中我們創(chuàng)建出來的線程是否還存在
Thread t 的生命周期和操作系統(tǒng)中對應(yīng)的線程的生命周期并不是完全一致的。
我們在定義一個線程類后,在調(diào)用t.start()方法之前,操作系統(tǒng)中是沒有我們創(chuàng)建出來的線程的。在線程類中的run()方法執(zhí)行完之后,我們在操作系統(tǒng)中創(chuàng)建出來的線程就被銷毀了!但是線程t對象還存在。
- 總而言之,在調(diào)用t.start()方法之前,在執(zhí)行run()方法之后,此時操作系統(tǒng)中是沒有我們創(chuàng)建出來的線程的。
- 在調(diào)用t.start()方法之后,在指向run()方法之前,此時操作系統(tǒng)中存在我們創(chuàng)建出來的線程 判斷該線程是由是后臺線程
如果一個線程是后臺線程,那么這個線程就不會進行進程退出
如果一個線程是前臺線程,那么這個這個線程就會影響到進程的退出。我們以上的代碼在創(chuàng)建線程,那些線程都是前臺線程,假如現(xiàn)在有前臺線程t1,t2, 現(xiàn)在即使main線程執(zhí)行結(jié)束,但是此時還不可以退出線程,必須要將t1,t2線程執(zhí)行結(jié)束之后,整個線程才會結(jié)束。
假如現(xiàn)在有兩個線程t1,t2,它們都是后臺線程,那么如果現(xiàn)在main線程執(zhí)行結(jié)束,整個進程就執(zhí)行結(jié)束,此時我們會強制停止t1,t2線程。
public class TestDemo18 { public static void main(String[] args) { Thread t = new Thread(()->{ System.out.println("hello thread"); }); t.start(); System.out.println(t.isDaemon()); } } //因為我們創(chuàng)建的是一個前臺線程,所以返回false
Thread的其他常見屬性
創(chuàng)建線程
創(chuàng)建線程:定義出一個線程類,然后啟動線程t.start(),其中start()方法決定系統(tǒng)是不是真的創(chuàng)建出線程。
線程的中斷
中斷線程簡單的可以理解成為就是讓該線程中的run()方法執(zhí)行結(jié)束。還有一個特殊的就是main線程,如果想要中斷main線程,那么就需要把main線程執(zhí)行完。
中斷線程方法一:設(shè)置一個標志位
public class TestDemo21 { public static void main(String[] args) { Thread t = new Thread(()->{ while(!Thread.currentThread().isInterrupted()) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); //啟動t線程 t.start(); //在main線程中中斷t線程 //5s之后中斷t線程 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }
運行結(jié)果:當(dāng)t線程中的sout語句被執(zhí)行5次之后,線程停止。
上面的這種寫法不夠嚴謹,只適用于該場合,如果化作是別的代碼場合的話,有可能不會終止線程。
這里用一種較好的方法,使用Thread類中自帶的檢查線程是否斷開。
Thread.interrputed() 這是一個靜態(tài)方法 Thread.currentThread().isinterrupted() 其中Thread.cerrentThread()可以獲得線程的引用。
t.interrupted()用于中斷線程
public class TestDemo21 { public static void main(String[] args) { Thread t = new Thread(()->{ while(!Thread.currentThread().isInterrupted()) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); //啟動t線程 t.start(); //在main線程中中斷t線程 //5s之后中斷t線程 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }
在我們上邊中斷線程,判斷標志位的時候,我們使用的是第三種方法。設(shè)置標志位的時候使用的是第一種方法。
在我們的日常開發(fā)中,經(jīng)常會用到Thread.currentThread().isInterrupted()來判斷標志位,判斷該線程是否被中斷。
還有一種方法判斷標志位,但是它是一個靜態(tài)方法,只能判斷一個類中只有一個線程的情況下,這個方法就是Thread.isinterrupted()方法。
Thread.currentThread().isinterrupted()這個方法判斷的是Thread的普通成員,每個實例都有一個標志位。
在我們以后就無腦使用Thread.currentThread().isInterrupted()方法,判斷線程是否中斷(標志位)
線程的等待
在前面我們也介紹到j(luò)oin()方法,這個方法就是讓線程與線程之間,有了一定的執(zhí)行順序。我們知道在多線程中的調(diào)度是隨機的,不確定的,多線程的執(zhí)行靠調(diào)度器安排,該調(diào)度器的安排是隨機的,不規(guī)律的。其實線程等待就是一個行之有效的方法,實際上就是控制線程執(zhí)行的先后順序。
public class TestDemo22 {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> public static void main(String[] args) throws InterruptedException {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> Thread t = new Thread(()->{<!--{C}%3C!%2D%2D%20%2D%2D%3E--> for(int i = 0;i<5;i++){<!--{C}%3C!%2D%2D%20%2D%2D%3E--> System.out.println("hello thread"); try {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> Thread.sleep(1000); } catch (InterruptedException e) {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> e.printStackTrace(); } } }); t.start(); t.join();//main線程調(diào)用t.join()方法,main線程就處于阻塞狀態(tài),當(dāng)t線程執(zhí)行完后,喚醒main線程執(zhí)行后序代碼 System.out.println("hello main"); }}
獲取線程的引用
使用方法Thread.currentTread()就可以該線程的實例。
public class TestDemo23 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { //獲取當(dāng)前線程的引用 //System.out.println(Thread.currentThread().getName()); //因為當(dāng)前使用的匿名內(nèi)部類是繼承自Thread類,Thread就是該匿名內(nèi)部類的父類,所以可以通過this得到當(dāng)前Thread的實例 System.out.println(this.getName()); } }; t.start(); } }
線程的休眠
其實線程休眠就是調(diào)用Thread.sleep()方法。
回顧之前的學(xué)習(xí)內(nèi)容,我們知道我們使用PCB描述一個進程,使用雙向鏈表來組織進程。這種說法是針對一個進程中只有一個線程的情況下。
那么如果一個進程中有多個線程存在,那么每個線程就有一個PCB,那么每個進程都會有一組PCB,
PCB上有一個字段為tgroupId,這個id就相當(dāng)于是進程的id,進程中的每個線程的tgroupId都是相同的。
那么進程控制塊(process contral block)和線程有什么關(guān)系呢?
其實在linux中是不區(qū)分進程和線程的,所謂的線程是程序員自己搞出來的,實際上linux只認進程控制塊(PCB),實際上線程就相當(dāng)于一個輕量級進程。
到此這篇關(guān)于Java中Thread類的使用和它的屬性的文章就介紹到這了,更多相關(guān)Java Thread類使用和屬性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于elasticsearch的match_phrase_prefix查詢詳解
這篇文章主要介紹了關(guān)于elasticsearch的match_phrase_prefix查詢問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03java實現(xiàn)微信掃碼登錄第三方網(wǎng)站功能(原理和代碼)
為避免繁瑣的注冊登陸,很多平臺和網(wǎng)站都會實現(xiàn)三方登陸的功能,增強用戶的粘性。這篇文章主要介紹了java實現(xiàn)微信掃碼登錄第三方網(wǎng)站功能(原理和代碼),避免做微信登錄開發(fā)的朋友們少走彎路2022-12-12如何優(yōu)雅的拋出Spring Boot注解的異常詳解
這篇文章主要給大家介紹了關(guān)于如何優(yōu)雅的拋出Spring Boot注解的異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12java中將一個List等分成n個list的工具方法(推薦)
下面小編就為大家?guī)硪黄猨ava中將一個List等分成n個list的工具方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03