Java線程Thread類

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,如不 進行額外的處理(手動關閉),將會出現這一問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章