本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
Thread類簡介
Thread
類是Java中實現多線程編程的基礎類。本篇博客就來介紹下Thread
類的常用API和常見用法。
Thread
類常用的方法如下:
- Thread.activeCount():這個方法用於返回當前線程的線程組中活動線程的數量,返回的值只是一個估計值,因為當此方法遍歷內部數據結構時,線程數可能會動態更改。)。
- Thread.checkAccess(): 檢驗當前正在執行的線程是否有權限修改thread的屬性,這個方法我們一般不自己進行調用,Thread類的set方法在進行屬性修改時都會先調用這個方法。
- Thread.currentThread():獲取當前正在運行的線程。
- Thread.dumpStack():輸出線程棧,一般在debug的時候調用。
- Thread.enumerate(Thread tarray[]):??使用場景。
- Thread.getAllStackTraces():獲取系統中所有線程的線程棧信息。
- thread.getName():獲取線程的名字。
- thread.getPriority():獲取線程的優先級。
- thread.getStackTrace():獲取堆棧信息。
- thread.getState():獲取線程狀態。
- thread.getThreadGroup():獲取線程所在線程組。
- thread.interrupt():使得指定線程中斷阻塞狀態,並將阻塞標志位置為true。
- thread.interrupted():測試當前線程是否被中斷。
- thread.isAlive():判斷線程是否還存活着。
- thread.isDaemon():判斷線程是否是守護線程。
- thread.join():在當前線程中加入指定線程,使得當前線程必須等待指定線程運行結束之后,才能結束。可以理解成線程插隊、等待該線程終止。
- Thread.sleep(long):強制線程睡眠一段時間。
- thread.start():啟動一個線程。
- thread.setName(name):設置線程的名字。
- thread.setPriority(priority):設置線程的優先級。
- thread.setDaemon(true):將指定線程設置為守護線程。
- thread.yield():使得當前線程退讓出CPU資源,把CPU調度機會分配給同樣線程優先級的線程。
- object.wait()、object.notify()、object.notifyAll():Object類提供的線程等待和線程喚醒方法。
還有Thread
類提供了功能豐富的構造函數,大家可以選合適的使用
示例代碼
public class MyThread {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
//這個方法返回的是當前線程所在線程組以及這個線程組的子線程組內活動的線程數
//這個值是一個估計值,所以這個方法的應用場景不大
int activeCount = Thread.activeCount();
System.out.println("當前系統中活動線程數["+activeCount+"]");
//向標准錯誤輸出流輸出當前的線程棧,不會阻斷程序的繼續執行
Thread.dumpStack();
//獲取所有線程棧信息
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
//獲取類加載器
ClassLoader contextClassLoader = thread.getContextClassLoader();
//獲取當前線程名字
String threadName = thread.getName();
System.out.println("current thread name["+threadName+"]");
//獲取當前線程ID
long threadId = thread.getId();
System.out.println("current thread id["+threadId+"]");
//獲取當前線程的優先級,一共有1~10總共10個優先級,這個優先級並不是在
//所有平台都生效的
int priority = thread.getPriority();
System.out.println("current thread priority["+priority+"]");
StackTraceElement[] stackTrace = thread.getStackTrace();
System.out.println("-------------stackTrace info--------------");
for (int i = 0; i < stackTrace.length; i++) {
StackTraceElement element = stackTrace[i];
System.out.println("className:["+element.getClassName()+"]");
System.out.println("fileName:["+element.getFileName()+"]");
System.out.println("line nunber:["+element.getLineNumber()+"]");
System.out.println("method name:["+element.getMethodName()+"]");
System.out.println("is native method:["+element.isNativeMethod()+"]");
System.out.println("------------------------------------------");
}
Thread.State state = thread.getState();
System.out.println("thread state:["+state+"]");
ThreadGroup threadGroup = thread.getThreadGroup();
String threadGroupName = threadGroup.getName();
System.out.println("thread group name:["+threadGroupName+"]");
//線程睡眠,調用sleep方法會使得線程進入timed_waiting狀態,如果線程已經
//獲得了鎖資源,調用sleep方法是不會釋放這個鎖的
Thread.sleep(2000,500);
Thread.sleep(1000);
TimeUnit.SECONDS.sleep(2);
Thread thread1 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
TimeUnit.SECONDS.sleep(100);
}
});
thread1.start();
thread1.join(50);
}
}
守護線程
守護線程可以理解為服務線程,他們的作用就是服務於其他用戶線程。當系統中不存在其他用戶線程時,這些守護線程也會自動消亡。比如JVM的垃圾清理線程就是守護線程。我們可以使用如下方法查看和設置線程是否是守護線程。
thread.isDaemon();
thread.setDaemon(true);
sleep方法
Thread類中有一個靜態的sleep方法,當一個執行中的線程調用了Thread的sleep方法后,調用線程會暫時讓出指定時間的執行權,也就是在這期間不參與CPU的調度,但是該線程所擁有的監視器資源,比如鎖還是持有不讓出的。指定的睡眠時間到了后該函數會正常返回,線程就處於就緒狀態,然后參與CPU的調度,獲取到CPU資源后就可以繼續運行了。如果在睡眠期間其他線程調用了該線程的interrupt()方法中斷了該線程,則該線程會在調用sleep方法的地方拋出InterruptedException異常而返回(進入waiting狀態線程的interrupt方法被調用,則這個線程會拋出InterruptedException異常)。
join方法
開發過程中我們可能會有這樣的需求:多個線程分別加載資源,等這些線程資源加載完畢之后對這些資源做統一匯總處理。join
方法就能實現類似的功能。
調用線程的join方法會使得調用線程進入waiting狀態,直到被調用的線程執行結束,調用線程才會重新獲得執行的機會。
public class MyThread {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
TimeUnit.SECONDS.sleep(100);
}
});
thread1.start();
thread1.join();
System.out.println("main thread end...");
}
}
上面的代碼中,main線程調用了thread1的join方法,main線程會被掛起進入waiting狀態,直到thread1執行完畢之后,main線程才有機會重新獲得執行機會。另外,如果main線程的interrupt方法被其他線程調用,那么main線程調用的join方法會拋出InterruptedException
異常。
join方法還有一個重載方法,這個方法可以指定超時時間。
thread1.join(50);
如果thread1線程在50ms內還沒執行完,main線程就可以重新獲得執行機會。
yeild方法
線程調用yield方法(這個方法是Thread的靜態方法)是在暗示讓這個線程讓出CPU資源,如果這個線程在執行一個CPU時間,已經執行到一半了,調用yield之后這個線程會放棄剩下的一半CPU時間回到就緒狀態。但是需要注意的是線程可以完全忽略yield方法的調用,也就是yield方法並不是每次都調用成功的:
- 退讓成功時,退讓線程會由Running(運行)轉為Runnable(就緒)狀態。
- 退讓了的線程,與其他同優先級級別的線程一樣,同樣有再次獲取CPU使用權的機會。
中斷
先貼上一段網友對線程中斷的總結。
- 如果線程不處於阻塞狀態,那么調用interrupt()方法僅僅是將[中斷標志位]將被置為true;
- 如果當前線程處於blocked阻塞(因為調用wait、sleep和join造成的)狀態時被interrupt了,那么[中斷標志位]將被清除,也就是設置為false,並且收到一個InterruptedException異常。
- 如果當前線程處於blocked阻塞(因為NIO的InterruptibleChannel進行的I/O操作造成的)狀態時被interrupt了,則會關閉channel,[中斷標志位]將會被置為true,並且當前線程會收到一個ClosedByInterruptException異常。
- 如果當前線程處於blocked阻塞(因為NIO的Selector造成的)狀態時被interrupt了,那么[中斷標志位]將被置為true,然后當前線程會立即從選擇器區域返回並返回值(可能為非零的值)。
需要說明的是:interrupt()方法並不是中斷線程,而是中斷阻塞狀態,也就是將線程的[中斷標志位]置為true。中斷后線程將繼續執行。
PS:事實上,interrupt方法只是改變目標線程的中斷狀態(interrupt status),而那些會拋出InterruptedException異常的方法,如wait、sleep、join等,都是在方法內部不斷地檢查中斷狀態的值。
幾個中斷方法對比:
- public static boolean interrupted():測試當前線程(這邊要注意的是這個方法返回的是當前正在執行的線程的中斷狀態,注意和isInterrupted的區別)是否已經中斷。線程的中斷狀態 由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false。
- public boolean isInterrupted():測試線程是否已經中斷。線程的中斷狀態 不受該方法的影響,也就是說不清除中斷標志。
public boolean isInterrupted() {
//傳遞false表示不清除標志位
return isInterrupted(false);
}
- public void interrupt():中斷線程,例如,當線程A運行時,線程B可以調用線程A的interrupt()方法來設置線程A的中斷標志為true並立即返回。設置標志僅僅是設置標志,線程A實際並沒有被中斷,它會繼續往下執行。如果線程A因為調用了wait系列函數、join方法或者sleep方法而被阻塞掛起,這時候若線程B調用線程A的interrupt()方法,線程A會在調用這些方法的地方拋出InterruptedException異常而返回。
當線程為了等待一些特定條件的到來時,一般會調用sleep函數、wait系列函數或者join()函數來阻塞掛起當前線程。比如一個線程調用了Thread. sleep(3000),那么調用線程會被阻塞,直到3s后才會從阻塞狀態變為激活狀態。但是有可能在3s內條件已被滿足,如果一直等到3s后再返回有點浪費時間,這時候可以調用該線程的interrupt()方法,強制sleep方法拋出InterruptedException異常而返回,線程恢復到激活狀態。