您好,登錄后才能下訂單哦!
這篇文章主要講解了“什么是Java中Thread構(gòu)造方法”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“什么是Java中Thread構(gòu)造方法”吧!
線程生命周期可以分為五個(gè)階段:
NEW
RUNNABLE
RUNNING
BLOCKED
TERMINATED
NEW
用new
創(chuàng)建一個(gè)Thread
對(duì)象時(shí),但是并沒有使用start()
啟動(dòng)線程,此時(shí)線程處于NEW
狀態(tài)。準(zhǔn)確地說,只是Thread
對(duì)象的狀態(tài),這就是一個(gè)普通的Java
對(duì)象。此時(shí)可以通過start()
方法進(jìn)入RUNNABLE
狀態(tài)。
RUNNABLE
進(jìn)入RUNNABLE
狀態(tài)必須調(diào)用start()
方法,這樣就在JVM
中創(chuàng)建了一個(gè)線程。但是,線程一經(jīng)創(chuàng)建,并不能馬上被執(zhí)行,線程執(zhí)行與否需要聽令于CPU
調(diào)度,也就是說,此時(shí)是處于可執(zhí)行狀態(tài),具備執(zhí)行的資格,但是并沒有真正執(zhí)行起來,而是在等待被調(diào)度。
RUNNABLE
狀態(tài)只能意外終止或進(jìn)入RUNNING
狀態(tài)。
RUNNING
一旦CPU
通過輪詢或其他方式從任務(wù)可執(zhí)行隊(duì)列中選中了線程,此時(shí)線程才能被執(zhí)行,也就是處于RUNNING
狀態(tài),在該狀態(tài)中,可能發(fā)生的狀態(tài)轉(zhuǎn)換如下:
進(jìn)入TERMINATED
:比如調(diào)用已經(jīng)不推薦的stop()
方法
進(jìn)入BLOCKED
:比如調(diào)用了sleep()
/wait()
方法,或者進(jìn)行某個(gè)阻塞操作(獲取鎖資源、磁盤IO
等)
進(jìn)入RUNNABLE
:CPU
時(shí)間片到,或者線程主動(dòng)調(diào)用yield()
BLOCKED
也就是阻塞狀態(tài),進(jìn)入阻塞狀態(tài)的原因很多,常見的如下:
磁盤IO
網(wǎng)絡(luò)操作
為了獲取鎖而進(jìn)入阻塞操作
處于BLOCKED
狀態(tài)時(shí),可能發(fā)生的狀態(tài)轉(zhuǎn)換如下:
進(jìn)入TERMINATED
:比如調(diào)用不推薦的stop()
,或者JVM
意外死亡
進(jìn)入RUNNABLE
:比如休眠結(jié)束、被notify()
/nofityAll()
喚醒、獲取到某個(gè)鎖、阻塞過程被interrupt()
打斷等
TERMINATED
TERMINATED
是線程的最終狀態(tài),進(jìn)入該狀態(tài)后,意味著線程的生命周期結(jié)束,比如在下列情況下會(huì)進(jìn)入該狀態(tài):
線程運(yùn)行正常結(jié)束
線程運(yùn)行出錯(cuò)意外結(jié)束
JVM
意外崩潰,導(dǎo)致所有線程都強(qiáng)制結(jié)束
Thread
構(gòu)造方法Thread
的構(gòu)造方法一共有八個(gè),這里根據(jù)命名方式分類,使用默認(rèn)命名的構(gòu)造方法如下:
Thread()
Thread(Runnable target)
Thread(ThreadGroup group,Runnable target)
命名線程的構(gòu)造方法如下:
Thread(String name)
Thread(Runnable target,Strintg name)
Thread(ThreadGroup group,String name)
Thread(ThreadGroup group,Runnable target,String name)
Thread(ThreadGroup group,Runnable target,String name,long stackSize)
但實(shí)際上所有的構(gòu)造方法最終都是調(diào)用如下私有構(gòu)造方法:
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
在默認(rèn)命名構(gòu)造方法中,在源碼中可以看到,默認(rèn)命名其實(shí)就是Thread-X
的命令(X為數(shù)字):
public Thread() { this((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L); } public Thread(Runnable target) { this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L); } private static synchronized int nextThreadNum() { return threadInitNumber++; }
而在命名構(gòu)造方法就是自定義的名字。
另外,如果想修改線程的名字,可以調(diào)用setName()
方法,但是需要注意,處于NEW
狀態(tài)的線程才能修改。
Thread
的所有構(gòu)造方法都會(huì)調(diào)用如下方法:
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
其中的一段源碼截取如下:
if (name == null) { throw new NullPointerException("name cannot be null"); } else { this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } }
可以看到當(dāng)前這里有一個(gè)局部變量叫parent
,并且賦值為currentThread()
,currentThread()
是一個(gè)native
方法。因?yàn)橐粋€(gè)線程被創(chuàng)建時(shí)的最初狀態(tài)為NEW
,因此currentThread()
代表是創(chuàng)建自身線程的那個(gè)線程,也就是說,結(jié)論如下:
一個(gè)線程的創(chuàng)建肯定是由另一個(gè)線程完成的
被創(chuàng)建線程的父線程是創(chuàng)建它的線程
也就是自己創(chuàng)建的線程,父線程為main
線程,而main
線程由JVM
創(chuàng)建。
另外,Thread
的構(gòu)造方法中有幾個(gè)具有ThreadGroup
參數(shù),該參數(shù)指定了線程位于哪一個(gè)ThreadGroup
,如果一個(gè)線程創(chuàng)建的時(shí)候沒有指定ThreadGroup
,那么將會(huì)和父線程同一個(gè)ThreadGroup
。main
線程所在的ThreadGroup
稱為main
。
stackSize
Thread
構(gòu)造方法中有一個(gè)stackSize
參數(shù),該參數(shù)指定了JVM
分配線程棧的地址空間的字節(jié)數(shù),對(duì)平臺(tái)依賴性較高,在一些平臺(tái)上:
設(shè)置較大的值:可以使得線程內(nèi)調(diào)用遞歸深度增加,降低StackOverflowError
出現(xiàn)的概率
設(shè)置較低的值:可以使得創(chuàng)建的線程數(shù)增多,可以推遲OutOfMemoryError
出現(xiàn)的時(shí)間
但是,在一些平臺(tái)上該參數(shù)不會(huì)起任何作用。另外,如果設(shè)置為0也不會(huì)起到任何作用。
Thread API
sleep()
sleep()
有兩個(gè)重載方法:
sleep(long mills)
sleep(long mills,int nanos)
但是在JDK1.5
后,引入了TimeUnit
,其中對(duì)sleep()
方法提供了很好的封裝,建議使用TimeUnit.XXXX.sleep()
去代替Thread.sleep()
:
TimeUnit.SECONDS.sleep(1); TimeUnit.MINUTES.sleep(3);
yield()
yield()
屬于一種啟發(fā)式方法,提醒CPU
調(diào)度器當(dāng)前線程會(huì)自愿放棄資源,如果CPU
資源不緊張,會(huì)忽略這種提醒。調(diào)用yield()
方法會(huì)使當(dāng)前線程從RUNNING
變?yōu)?code>RUNNABLE狀態(tài)。
關(guān)于yield()
與sleep()
的區(qū)別,區(qū)別如下:
sleep()
會(huì)導(dǎo)致當(dāng)前線程暫停指定的時(shí)間,沒有CPU
時(shí)間片的消耗
yield()
只是對(duì)CPU
調(diào)度器的一個(gè)提示,如果CPU
調(diào)度器沒有忽略這個(gè)提示,會(huì)導(dǎo)致線程上下文的切換
sleep()
會(huì)使線程短暫阻塞,在給定時(shí)間內(nèi)釋放CPU
資源
如果yield()
生效,yield()
會(huì)使得從RUNNING
狀態(tài)進(jìn)入RUNNABLE
狀態(tài)
sleep()
會(huì)幾乎百分百地完成給定時(shí)間的休眠,但是yield()
的提示不一定能擔(dān)保
一個(gè)線程調(diào)用sleep()
而另一個(gè)線程調(diào)用interrupt()
會(huì)捕獲到中斷信號(hào),而yield
則不會(huì)
setPriority()
線程與進(jìn)程類似,也有自己的優(yōu)先級(jí),理論上來說,優(yōu)先級(jí)越高的線程會(huì)有優(yōu)先被調(diào)度的機(jī)會(huì),但實(shí)際上并不是如此,設(shè)置優(yōu)先級(jí)與yield()
類似,也是一個(gè)提醒性質(zhì)的操作:
對(duì)于root
用戶,會(huì)提醒操作系統(tǒng)想要設(shè)置的優(yōu)先級(jí)別,否則會(huì)被忽略
如果CPU
比較忙,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的CPU
時(shí)間片,但是空閑時(shí)優(yōu)先級(jí)的高低幾乎不會(huì)有任何作用
所以,設(shè)置優(yōu)先級(jí)只是很大程度上讓某個(gè)線程盡可能獲得比較多的執(zhí)行機(jī)會(huì),也就是讓線程自己盡可能被操作系統(tǒng)調(diào)度,而不是設(shè)置了高優(yōu)先級(jí)就一定優(yōu)先運(yùn)行,或者說優(yōu)先級(jí)高的線程比優(yōu)先級(jí)低的線程就一定優(yōu)先運(yùn)行。
設(shè)置優(yōu)先級(jí)直接調(diào)用setPriority()
即可,OpenJDK 11
源碼如下:
public final void setPriority(int newPriority) { this.checkAccess(); if (newPriority <= 10 && newPriority >= 1) { ThreadGroup g; if ((g = this.getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } this.setPriority0(this.priority = newPriority); } } else { throw new IllegalArgumentException(); } }
可以看到優(yōu)先級(jí)處于[1,10]
之間,而且不能設(shè)置為大于當(dāng)前ThreadGroup
的優(yōu)先級(jí),最后通過native
方法setPriority0
設(shè)置優(yōu)先級(jí)。
一般情況下,不會(huì)對(duì)線程的優(yōu)先級(jí)設(shè)置級(jí)別,默認(rèn)情況下,線程的優(yōu)先級(jí)為5,因?yàn)?code>main線程的優(yōu)先級(jí)為5,而且main
為所有線程的父進(jìn)程,因此默認(rèn)情況下線程的優(yōu)先級(jí)也是5。
interrupt()
interrupt()
是一個(gè)重要的API
,線程中斷的API
有如下三個(gè):
void interrupt()
boolean isInterrupted()
static boolean interrupted()
下面對(duì)其逐一進(jìn)行分析。
interrupt()
一些方法調(diào)用會(huì)使得當(dāng)前線程進(jìn)入阻塞狀態(tài),比如:
Object.wait()
Thread.sleep()
Thread.join()
Selector.wakeup()
而調(diào)用interrupt()
可以打斷阻塞,打斷阻塞并不等于線程的生命周期結(jié)束,僅僅是打斷了當(dāng)前線程的阻塞狀態(tài)。一旦在阻塞狀態(tài)下被打斷,就會(huì)拋出一個(gè)InterruptedException
的異常,這個(gè)異常就像一個(gè)信號(hào)一樣通知當(dāng)前線程被打斷了,例子如下:
public static void main(String[] args) throws InterruptedException{ Thread thread = new Thread(()->{ try{ TimeUnit.SECONDS.sleep(10); }catch (InterruptedException e){ System.out.println("Thread is interrupted."); } }); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); }
會(huì)輸出線程被中斷的信息。
isInterrupted()
isInterrupted()
可以判斷當(dāng)前線程是否被中斷,僅僅是對(duì)interrupt()
標(biāo)識(shí)的一個(gè)判斷,并不會(huì)影響標(biāo)識(shí)發(fā)生任何改變(因?yàn)檎{(diào)用interrupt()
的時(shí)候會(huì)設(shè)置內(nèi)部的一個(gè)叫interrupt flag
的標(biāo)識(shí)),例子如下:
public static void main(String[] args) throws InterruptedException{ Thread thread = new Thread(()->{ while (true){} }); thread.start(); TimeUnit.SECONDS.sleep(1); System.out.println("Thread is interrupted :"+thread.isInterrupted()); thread.interrupt(); System.out.println("Thread is interrupted :"+thread.isInterrupted()); }
輸出結(jié)果為:
Thread is interrupted :false Thread is interrupted :true
另一個(gè)例子如下:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { System.out.println("In catch block thread is interrupted :" + isInterrupted()); } } } }; thread.start(); TimeUnit.SECONDS.sleep(1); System.out.println("Thread is interrupted :" + thread.isInterrupted()); thread.interrupt(); TimeUnit.SECONDS.sleep(1); System.out.println("Thread is interrupted :" + thread.isInterrupted()); }
輸出結(jié)果:
Thread is interrupted :false In catch block thread is interrupted :false Thread is interrupted :false
一開始線程未被中斷,結(jié)果為false
,調(diào)用中斷方法后,在循環(huán)體內(nèi)捕獲到了異常(信號(hào)),此時(shí)會(huì)Thread
自身會(huì)擦除interrupt
標(biāo)識(shí),將標(biāo)識(shí)復(fù)位,因此捕獲到異常后輸出結(jié)果也為false
。
interrupted()
這是一個(gè)靜態(tài)方法,調(diào)用該方法會(huì)擦除掉線程的interrupt
標(biāo)識(shí),需要注意的是如果當(dāng)前線程被打斷了:
第一次調(diào)用interrupted()
會(huì)返回true
,并且立即擦除掉interrupt
標(biāo)識(shí)
第二次包括以后的調(diào)用永遠(yuǎn)都會(huì)返回false
,除非在此期間線程又一次被打斷
例子如下:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { System.out.println(Thread.interrupted()); } } }; thread.setDaemon(true); thread.start(); TimeUnit.MILLISECONDS.sleep(2); thread.interrupt(); }
輸出(截取一部分):
false false false true false false false
可以看到其中帶有一個(gè)true
,也就是interrupted()
判斷到了其被中斷,此時(shí)會(huì)立即擦除中斷標(biāo)識(shí),并且只有該次返回true
,后面都是false
。
關(guān)于interrupted()
與isInterrupted()
的區(qū)別,可以從源碼(OpenJDK 11
)知道:
public static boolean interrupted() { return currentThread().isInterrupted(true); } public boolean isInterrupted() { return this.isInterrupted(false); } @HotSpotIntrinsicCandidate private native boolean isInterrupted(boolean var1);
實(shí)際上兩者都是調(diào)用同一個(gè)native
方法,其中的布爾變量表示是否擦除線程的interrupt
標(biāo)識(shí):
true
表示想要擦除,interrupted()
就是這樣做的
false
表示不想擦除,isInterrupted()
就是這樣做的
join()
join()
簡(jiǎn)介join()
與sleep()
一樣,都是屬于可以中斷的方法,如果其他線程執(zhí)行了對(duì)當(dāng)前線程的interrupt
操作,也會(huì)捕獲到中斷信號(hào),并且擦除線程的interrupt
標(biāo)識(shí),join()
提供了三個(gè)API
,分別如下:
void join()
void join(long millis,int nanos)
void join(long mills)
一個(gè)簡(jiǎn)單的例子如下:
public class Main { public static void main(String[] args) throws InterruptedException { List<Thread> threads = IntStream.range(1,3).mapToObj(Main::create).collect(Collectors.toList()); threads.forEach(Thread::start); for (Thread thread:threads){ thread.join(); } for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+" # "+i); shortSleep(); } } private static Thread create(int seq){ return new Thread(()->{ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+" # "+i); shortSleep(); } },String.valueOf(seq)); } private static void shortSleep(){ try{ TimeUnit.MILLISECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); } } }
輸出截取如下:
2 # 8 1 # 8 2 # 9 1 # 9 main # 0 main # 1 main # 2 main # 3 main # 4
線程1和線程2交替執(zhí)行,而main
線程會(huì)等到線程1和線程2執(zhí)行完畢后再執(zhí)行。
Thread
中有一個(gè)過時(shí)的方法stop
,可以用于關(guān)閉線程,但是存在的問題是有可能不會(huì)釋放monitor
的鎖,因此不建議使用該方法關(guān)閉線程。線程的關(guān)閉可以分為三類:
正常關(guān)閉
異常退出
假死
線程運(yùn)行結(jié)束后,就會(huì)正常退出,這是最普通的一種情況。
通過捕獲中斷信號(hào)去關(guān)閉線程,例子如下:
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(){ @Override public void run() { System.out.println("work..."); while(!isInterrupted()){ } System.out.println("exit..."); } }; t.start(); TimeUnit.SECONDS.sleep(5); System.out.println("System will be shutdown."); t.interrupt(); }
一直檢查interrupt
標(biāo)識(shí)是否設(shè)置為true
,設(shè)置為true
則跳出循環(huán)。另一種方式是使用sleep()
:
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(){ @Override public void run() { System.out.println("work..."); while(true){ try{ TimeUnit.MILLISECONDS.sleep(1); }catch (InterruptedException e){ break; } } System.out.println("exit..."); } }; t.start(); TimeUnit.SECONDS.sleep(5); System.out.println("System will be shutdown."); t.interrupt(); }
volatile
由于interrupt
標(biāo)識(shí)很有可能被擦除,或者不會(huì)調(diào)用interrupt()
方法,因此另一種方法是使用volatile
修飾一個(gè)布爾變量,并不斷循環(huán)判斷:
public class Main { static class MyTask extends Thread{ private volatile boolean closed = false; @Override public void run() { System.out.println("work..."); while (!closed && !isInterrupted()){ } System.out.println("exit..."); } public void close(){ this.closed = true; this.interrupt(); } } public static void main(String[] args) throws InterruptedException { MyTask t = new MyTask(); t.start(); TimeUnit.SECONDS.sleep(5); System.out.println("System will be shutdown."); t.close(); } }
線程執(zhí)行單元中是不允許拋出checked
異常的,如果在線程運(yùn)行過程中需要捕獲checked
異常并且判斷是否還有運(yùn)行下去的必要,可以將checked
異常封裝為unchecked
異常,比如RuntimeException
,拋出從而結(jié)束線程的生命周期。
所謂假死就是雖然線程存在,但是卻沒有任何的外在表現(xiàn),比如:
沒有日志輸出
不進(jìn)行任何的作業(yè)
等等,雖然此時(shí)線程是存在的,但看起來跟死了一樣,事實(shí)上是沒有死的,出現(xiàn)這種情況,很大可能是因?yàn)榫€程出現(xiàn)了阻塞,或者兩個(gè)線程爭(zhēng)奪資源出現(xiàn)了死鎖。
這種情況需要借助一些外部工具去判斷,比如VisualVM
、jconsole
等等,找出存在問題的線程以及當(dāng)前的狀態(tài),并判斷是哪個(gè)方法造成了阻塞。
感謝各位的閱讀,以上就是“什么是Java中Thread構(gòu)造方法”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)什么是Java中Thread構(gòu)造方法這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。