在學習了第一節之後,我們來看一下Thread常見api的學習。
static methods
以下方法基本上看源碼代碼,以及翻譯一下源碼的英文註釋 。
1 sleep()方法,
使當前正在運行的線程睡眠多少毫秒,這取決於你的系統的時間定時器和調度器,這個線程
不會失去任何監視器的所有權 (線程會阻塞,但是不會釋放資源和鎖)
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
* @param millis
這個參數是以毫秒爲級別的
* the length of time to sleep in milliseconds
* @throws IllegalArgumentException
如果是負數就會拋異常
* if the value of {@code millis} is negative
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds plus the specified
* number of nanoseconds, subject to the precision and accuracy of system
* timers and schedulers. The thread does not lose ownership of any
* monitors.
* @param millis
* the length of time to sleep in milliseconds
* @param nanos
如果不是[0,999999] 裏面就會拋出異常
* {@code 0-999999} additional nanoseconds to sleep
* @throws IllegalArgumentException
* if the value of {@code millis} is negative, or the value of
* {@code nanos} is not in the range {@code 0-999999}
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
其實也就是多加入了一個納秒級別的,但是並不是加了就生效還是和nanos>=500000
一樣
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
2 yield()方法
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*/
通知這個調度器,讓這個正在運行的當前線程去讓出當前的處理器
也就是說使當前線程從執行狀態(運行狀態)變爲可執行態(就緒狀態)。
cpu會從衆多的可執行態裏選擇,就是剛剛的那個線程還是有可能會被再次執行到的,
並不是說一定會執行其他線程而該線程在下一次中不會執行到了。
public static native void yield();
3 interrupted()方法 英文註釋太長了,樓主直接給翻譯過來註釋上去了
/**
測試當前線程是否已被中斷,此方法會清楚線程的中斷狀態。換句話說,如果連續兩次調用
此方法,則第二次調用將返回false(除非當前線程在第一次調用已清除其中斷狀態之後且在
第二次調用檢查之前再次中斷)
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
作用是測試當前線程是否被中斷(檢查中斷標誌),返回一個boolean並清除中斷狀態,
第二次再調用時中斷狀態已經被清除,將返回一個false。
Instance Methods
這裏源碼裏面有參考另外兩個實例方法 public void interrupt() 以及 public boolean isInterrupted(),一起講述一個新知識:
如何優雅的停止一個線程?首先來看一下interrupt()方法
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();}
具體的英文註釋樓主大概解釋一下,Java線程裏面都是協作形式的,協作式的意思就是一個線程不會立馬要另外一個線程停止,例如 A線程中 調用了 B.interrupt()方法,白話來說就是A線程現在給你B線程打了一個招呼你要停止了,至於你B線程停止與否還是看你B線程自己。那麼interrupt()方法具體有哪些用處呢?,翻譯源碼可知
中斷調用該方法的線程,除非當前線程正在中斷自己,否則checkAccess方法有可能拋出SecurityException異常。
(1)如果當前線程由於調用Object的wait(),或者Thread的join和sleep方法阻塞,則退出阻塞且中斷狀態將被清除,並且拋出InterrruptedException異常,(同時中斷狀態爲false),這樣,我們就可以捕捉到中斷異常,並根據實際情況對該線程從阻塞方法中異常退出而進行一些處理。
(2)如果線程由於InterruptibleChannel的IO操作阻塞,則通道將關閉,線程設置中斷狀態,並且線程收到一個ClosedInterruptException異常。
(3)如果線程由於Selector阻塞,線程將處於中斷狀態,並且從selection操作立刻返回,可能是一個非零值。除此之外,線程將被設置中斷狀態。
總結來說 :interrupt方法有兩個作用:(1)將線程的中斷狀態設置爲true(默認爲false),調用這個方法並不是一個會把線程停止,只是把中斷狀態設置爲true,至於停止與否還需要該線程自己來處理這個中斷狀態 (2)同上面描述的第一個紅字作用。
再看一下isInterrupted() 方法 public boolean isInterrupted() 調用
檢測調用該方法的線程是否被中斷線程一旦被中斷,該方法返回true中斷狀態不會被清除。下面用代碼測試一下如何停止線程
static class StopThread extends Thread{
@Override
public void run() {
//當該線程調用interrupt()方法之後會把中斷狀態改爲true
while (!interrupted()){
//默認中斷狀態爲false,如果線程一直alive 就會一直運行while循環體內容
System.out.println("Thread name = "+getName());
}
System.out.println("Thread is stop");
}
}
public static void main(String[] args) {
StopThread thread = new StopThread();
thread.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
如果改成下面代碼,就會走入異常,並且把中斷狀態重新設置爲false
另外 stop () 停止線程這個方法已經不建議使用,因爲太過暴力和絕對,直接死掉不會釋放相關資源。
suspend()使線程掛起不會釋放類似鎖這樣的資源 resume()使線程恢復如果之前沒有使用suspend暫停線程則不起作用,那麼suspend不釋放資源,如果一條線程將去resume目標線程之前嘗試持有這個重要的系統資源再去resume目標線程,那麼就和suspend掛起的線程死鎖了,所以這三個方法不建議使用了 !!!
再來一個判斷是否線程存活的方法
最後一個常見的Join方法
/**
* Waits for this thread to die.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
如果一個線程A調用了 B線程的join()方法,則讓出CPU的執行權,讓B線程執行完畢之後 A才接着執行。
t.join(); //使調用線程 t 在此之前執行完畢。
t.join(1000); //等待 t 線程,等待時間是1000毫秒。Join方法實現是通過wait(小提示:Object 提供的方法)。 當main線程調用t.join時候,main線程會獲得線程對象t的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 ,比如退出後。這就意味着main 線程調用t.join時,必須能夠拿到線程t對象的鎖,下面看個例子
public class JoinTest {
static class JumpQueue implements Runnable{
private Thread thread;
JumpQueue(Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
//此時構造初始完畢 thread 傳入進來是主線程main 則main執行完畢之後當先子線程0才能拿到返回對象 0 繼續執行,否則一直等待main執行
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" terminate subclass");
}
}
public static void main(String[] args) throws InterruptedException {
Thread previous = Thread.currentThread();
//首先第一步獲取得到的previous是主線程
for(int i= 0;i<10;i++){
Thread thread = new Thread(new JumpQueue(previous),String.valueOf(i));
//第一次得到的是子線程 thread [0 ,5,main] 線程主是main線程
System.out.println(previous.getName()+" jump a queue the thread :"+thread.getName());
//所以第一次打印 main jump a queue the thread : 0
thread.start();
previous = thread;
}
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" terminate class");
}
}
基本上Thread類常用API已經分析了,Thread類裏面有個枚舉類 public enum State { NEW, RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED }
NEW(新建/初始狀態): 新建的一個Thread,還沒有調用start()方法
RUNNABLE (可運行狀態): 調用了start()方法,變成可運行的線程狀態,但是否運行還是取決於操作系統和資源的調度
BLOCKED (阻塞狀態): 比如線程裏面調用 t.join() Thread.sleep() 等
WAITING/TIME_WAITING(等待狀態) : 具有指定等待時間的等待線程的hread狀態
TERMINATED(結束狀態):線程運行完了/異常結束都算結束狀態。
總結 :
wait()、notify()/notifyAll()、sleep()、yield()、join() 的理解
1)sleep()是Thread的靜態方法,不用在同步環境使用,作用當前線程,不釋放鎖。
2)yield()是Thread的靜態方法,作用當前線程,釋放當前線程持有的CPU資源,將CPU讓給優先級不低於自己的線程用,調用後進入就緒狀態,不釋放鎖。
3)join()是Thread的實例方法,作用是阻塞當前線程,讓新加入的線程運行,由於join()裏面源碼還是調用的wait()方法
下面重點說一下 wait,notify,nitifyAll
1、wait()、notify/notifyAll() 方法是Object的本地final方法,無法被重寫。
2、wait()使當前線程阻塞,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用,即,一般在synchronized 同步代碼塊裏使用 wait()、notify/notifyAll() 方法。
3、 由於 wait()、notify/notifyAll() 在synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。當線程執行wait()方法時候,會釋放當前的鎖,然後讓出CPU,進入等待狀態。只有當 notify/notifyAll() 被執行時候,纔會喚醒一個或多個正處於等待狀態的線程,然後繼續往下執行,直到執行完synchronized 代碼塊的代碼或是中途遇到wait() ,再次釋放鎖。也就是說,notify/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,儘量在使用了notify/notifyAll() 後立即退出臨界區,以喚醒其他線程讓其獲得鎖。
4、notify 和wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那麼B線程是無法被喚醒的。
5、notify 和 notifyAll的區別 notify方法只喚醒一個等待(對象的)線程並使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決於操作系統對多線程管理的實現。notifyAll 會喚醒所有等待(對象的)線程,儘管哪一個線程將會第一個處理取決於操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll 方法。比如在生產者-消費者裏面的使用,每次都需要喚醒所有的消費者或是生產者,以判斷程序是否可以繼續往下執行。
6、在多線程中要測試某個條件的變化,使用if 還是while?
要注意,notify喚醒沉睡的線程後,線程會接着上次的執行繼續往下執行。所以在進行條件判斷時候,可以先把 wait 語句忽略不計來進行考慮;顯然,要確保程序一定要執行,並且要保證程序直到滿足一定的條件再執行,要使用while進行等待,直到滿足條件才繼續往下執行。