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