Thread方法
start()
start()用來啓動一個線程,當調用start方法後,系統纔會開啓一個新的線程來執行用戶定義的子任務,在這個過程中,會爲相應的線程分配需要的資源
run()
run()方法是不需要用戶來調用的,當通過start方法啓動一個線程之後,當線程獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。
sleep()
sleep相當於讓線程睡眠,交出CPU,讓CPU去執行其他的任務。
但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。
當線程睡眠時間滿後,不一定會立即得到執行,因爲此時可能CPU正在執行其他的任務。所以說調用sleep方法相當於讓線程進入阻塞狀態
yield()
調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。
注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。
join()
調用thread.join方法,則main方法會等待該thread線程執行完畢或者等待一定的時間。如果調用的是無參join方法,則等待thread執行完畢,如果調用的是指定了時間參數的join方法,則等待參數指定的時間。
當調用thread1.join()方法後,main線程會進入等待,然後等待thread1執行完之後再繼續執行。
相當與thread1線程插入到當前執行的線程中,當前線程釋放佔有的鎖,進入阻塞狀態,CPU執行thread1。
實際上調用join方法是調用了Object的wait方法,wait方法會讓線程進入阻塞狀態,並且會釋放線程佔有的鎖,並交出CPU執行權限,所以join方法同樣會讓線程釋放對一個對象持有的鎖。
interrupt()
單獨調用interrupt方法可以使得處於阻塞狀態的線程拋出一個異常,它可以用來中斷一個正處於阻塞狀態的線程;另外,通過interrupt方法和isInterrupted()方法來停止正在運行的線程。
通過interrupt方法可以中斷處於阻塞狀態的線程。
直接調用interrupt方法不能中斷正在運行中的線程。
isInterrupted()能夠中斷正在運行的線程,因爲調用interrupt方法相當於將中斷標誌位置爲true,那麼可以通過調用isInterrupted()判斷中斷標誌是否被置位來中斷線程的執行。
MyThread thread = test.new MyThread();
thread.start();
.....
thread.interrupt();
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted() && i<Integer.MAX_VALUE){ //!isInterrupted()判斷終端標誌
System.out.println(i+" while循環");
i++;
}
}
}
一般會在MyThread類中增加一個屬性 isStop來標誌,通過設置標誌位的值來控制相關邏輯中的過程
setDaemon和isDaemon
設置線程是否成爲守護線程和判斷線程是否是守護線程。
守護線程和用戶線程的區別在於:守護線程依賴於創建它的線程,而用戶線程則不依賴。
舉個簡單的例子:如果在main線程中創建了一個守護線程,當main 方法運行完畢之後,守護線程也會隨着消亡。而用戶線程則不會,用戶線程會一直運行直到其運行完畢。在JVM中,像垃圾收集器線程就是守護線程。
線程間協作
wait(),notify(),notifyall()
Object類中三個方法:
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
1、wait()、notify()和notifyAll()方法是本地方法,並且爲final方法,無法被重寫。
2、調用某個對象的wait()方法能讓當前線程阻塞,並且當前線程必須擁有此對象的monitor(即鎖)
3、調用某個對象的notify()方法能夠喚醒一個正在等待這個對象的monitor的線程,如果有多個線程都在等待這個對象的monitor,則只能喚醒其中一個線程(隨機);
4、調用notifyAll()方法能夠喚醒所有正在等待這個對象的monitor的線程;
Q&A:爲何這三個不是Thread類聲明中的方法,而是Object類中聲明的方法?
由於每個對象都擁有monitor(即鎖),所以讓當前線程等待某個對象的鎖,當然應該通過這個對象來操作了。而不是用當前線程來操作,因爲當前線程可能會等待多個線程的鎖,如果通過線程來操作,就非常複雜了
wait方法
調用某個對象的wait()方法,當前線程必須擁有這個對象的monitor(即鎖),因此調用wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。
調用某個對象的wait()方法,相當於讓當前線程交出此對象的monitor,然後進入等待狀態,等待後續再次獲得此對象的鎖(Thread類中的sleep方法使當前線程暫停執行一段時間(讓出CPU執行權限),從而讓其他線程有機會繼續執行,但它並不釋放對象鎖);
notify方法
notify()方法能夠喚醒一個正在等待該對象的monitor的線程,當有多個線程都在等待該對象的monitor的話,則只能喚醒其中一個線程,具體喚醒哪個線程則不得而知。
調用某個對象的notify()方法,當前線程也必須擁有這個對象的monitor,因此調用notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。
nofityAll()方法
能夠喚醒所有正在等待該對象的monitor的線程,
★注意:
notify()和notifyAll()方法只是喚醒等待該對象的monitor的線程,並不決定哪個線程能夠獲取到monitor。
一個線程被喚醒不代表立即獲取了對象的monitor,只有等調用完notify()或者notifyAll()並退出synchronized塊,釋放對象鎖後,其餘線程纔可獲得鎖執行。
Condition
用來替代傳統的Object的wait()、notify()實現線程間的協作更加安全和高效。
Condition是個接口,基本的方法就是await()和signal()方法;
Condition**依賴於Lock接口**,生成一個Condition的基本代碼是lock.newCondition()
調用Condition的await()和signal()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間纔可以使用
Conditon中的await()對應Object的wait();
Condition中的signal()對應Object的notify();
Condition中的signalAll()對應Object的notifyAll()。
線程的生命活動週期之內各方法調用及狀態變換的關係
用戶線程(User Thread)與守護線程(Daemon Thread)
java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程)
任何一個守護線程都是整個JVM中所有非守護線程的保姆:Daemon的作用是爲其他線程的運行提供便利服務
只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;只有當最後一個非守護線程結束時,因爲沒有了被守護者,Daemon也就沒有工作可做了,也就沒有繼續運行程序的必要了,守護線程隨着JVM一同結束工作
Thread daemonTread = new Thread();
daemonThread.setDaemon(true); // 設定 daemonThread 爲 守護線程,默認false(非守護線程)
daemonThread.isDaemon();// 驗證當前線程是否爲守護線程,返回 true 則爲守護線程
☆注意:
1) thread.setDaemon(true)必須在thread.start()之前設置,否則會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置爲守護線程。
(2) Daemon子線程也是Daemon線程。
(3) 不是所有的應用都可以分配給Daemon來進行服務,比如讀寫操作或者計算邏輯,因爲你不可能知道在所有的User完成之前,Daemon是否已經完成了預期的服務任務。
JRE判斷程序是否執行結束的標準是所有的前臺執線程行完畢了,而不管後臺線程的狀態。
定義:守護線程–也稱“服務線程”,在沒有用戶線程可服務時會自動離開。
優先級:守護線程的優先級比較低,用於爲系統中的其它對象和線程提供服務。
設置:通過setDaemon(true)來設置線程爲“守護線程”。
示例: 垃圾回收線程就是一個經典的守護線程,當我們的程序中不再有任何運行的
Thread,程序就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收線程是
JVM上僅剩的線程時,垃圾回收線程會自動離開。
爲什麼要用守護線程?
我 們知道靜態變量是ClassLoader級別的,如果Web應用程序停止,這些靜態變量也會從JVM中清除。但是線程則是JVM級別的,如果你在Web 應用中啓動一個線程,這個線程的生命週期並不會和Web應用程序保持同步。也就是說,即使你停止了Web應用,這個線程依舊是活躍的。正是因爲這個很隱晦 的問題,所以很多有經驗的開發者不太贊成在Web應用中私自啓動線程。
Spring 爲JDK Timer和Quartz Scheduler所提供的TimerFactoryBean和SchedulerFactoryBean能夠和Spring容器的生命週期關聯,在 Spring容器啓動時啓動調度器,而在Spring容器關閉時,停止調度器。而如果你在程序中直接使用Timer或Scheduler,如不 進行額外的處理(手動關閉),將會出現這一問題。