看《Java特種兵》的時候發現,Thread.join可使線程進入WAITING狀態,再結合姊妹篇線程的狀態咱們能夠了解到,有兩個類狀態很是接近:WAITING(TIMED_WAITING) 與 BLOCKED,這二者都會讓線程看上去「阻塞」在某處了。java
何時線程會進入WAITING(無限期等待)的狀態中呢?經常使用的有兩個,分別是①Object.wait without timeout,②Thread.join without timeout【另外還有③LockSupport的park方法,④Conditon的await方法】;TIMED_WAITING除了①Object.wait with timeout、②Thread.join with timeout,還須要添加一條③Thread.sleep方法【另外還有④LockSupport的parkNanos方法,帶有時間】。安全
在進入WAITING狀態前,線程會將持有的鎖先釋放掉。WAITING狀態中的線程須要被其餘線程對同一個對象調用notify()或notifyAll()方法才能喚醒。被notifyAll()喚醒後的線程,拿不到鎖的線程將進入BLOCKED狀態,直到它們拿到鎖爲止。簡而言之,WAITING類狀態中的線程和BLOCKED狀態中的線程的區別在於:WAITING狀態的線程須要被其餘線程喚醒;BLOCKED中的線程,須要等待其餘線程釋放鎖,此處的鎖特指synchronized塊。app
見下圖ide
Join爲何會使線程進入WAITING(TIMED_WAITING) 狀態中呢?咱們看一下底層代碼oop
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; } } }
從十二、20行中能夠看出,join方法底層調用的就是wait方法。測試
一樣會使線程進入TIMED_WAITING狀態中的sleep和wait方法,它們之間有什麼區別呢?ui
wait()是非靜態方法this
public final void wait() throws InterruptedException { //...}
sleep()是靜態方法線程
public static void sleep(long millis, int nanos) throws InterruptedException { //... }
synchronized(obj) { while (!condition) { obj.wait(); } }
這裏引用一段《Effective Java》
始終應該使用wait循環模式來調用wait方法;永遠不要在循環以外調用wait方法。循環會在等待以前和以後測試條件。
在等待以前測試條件,當條件已經成立時就跳過等待,這對於確保活性(liveness)是必要的。若是條件已經成立,而且在線程等待以前,notify (或者notifyAll)方法已經被調用, 則沒法保證該線程將會從等待中甦醒過來。
在等待以後測試條件,若是條件不成立的話繼續等待,這對於確保安全性(safety)是必要的。當條件不成立的時候,若是線程繼續執行,則可能會破壞被鎖保護的約束關係。當條件不成立時,有下面一些理由可以使一個線程甦醒過來:
- 另外一個線程可能已經獲得了鎖,而且從一個線程調用notify那一刻起,到等待線程甦醒過來的這段時間中,獲得鎖的線程已經改變了受保護的狀態。 - 條件並不成立,可是另外一個線程可能意外地或惡意地調用了 notify。在公有可訪問的對象上等待,這些類實際上把本身暴露在了這種危險的境地中。公有可訪問對象的同步方法中包含的wait都會出現這樣知問題。 - 通知線程(notifying thread)在喚醒等待線程時可能會過分「大方」。例如,即便只有某一些等待線程的條件已經被知足,可是通知線程可能仍然調用notifyAll。 - 在沒有通知的狀況下,等待線程也可能(但不多)會甦醒過來。這被稱爲「僞喚醒 (spurious wakeup)」
咱們針對【跳過等待】和【繼續等待】舉個形象的例子:code
針對【前置判斷,跳過等待】:若是是兩個狙擊手,在同時等待一我的(鎖),判斷條件是這我的還活着。若是沒有前置的判斷,在等待前不校驗這我的是否活着,那麼當狙擊手甲殺死目標並通知狙擊手乙以後,乙才進入等待狀態,那麼乙將會死等一個已經被殺死的目標,乙將失去活性(liveness)。
針對【後置判斷,繼續等待】:仍是兩個狙擊手,若是他們被喚醒後,沒有後置校驗,那麼將致使好笑的錯誤,好比狙擊手甲已經將目標殺死了,狙擊手乙被喚醒後,沒有再校驗條件,直接開槍殺人,將會殺死目標兩次。若是是冪等的還則罷了,不冪等的將致使錯誤。
綜上所述,wait先後都要校驗,而最好的辦法就是循環。
從上文中咱們已經知道了,join可使線程處於WAITING狀態,那問題來了,是子線程處於WAITING狀態仍是父線程處於WAITING狀態?咱們作個小試驗:
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread.currentThread().setName("TestJoin main...."); Thread joinThread = new Thread(new Runnable() { @Override public void run() { for (; ; ) { } } }, "join thread"); joinThread.start(); joinThread.join(); } }
按照在線程的狀態中提供的方法,咱們能夠獲得:
子線程即join的線程依舊是RUNNABLE狀態
"join thread" #10 prio=5 os_prio=31 tid=0x00007fca1b801000 nid=0x5503 runnable [0x0000700001725000]
java.lang.Thread.State: RUNNABLE
at com.meituan.java8.thread.TestJoin$1.run(TestJoin.java:13) at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
父線程(在此例中是主線程)爲WAITING狀態
"TestJoin main...." #1 prio=5 os_prio=31 tid=0x00007fca1c003000 nid=0x1903 in Object.wait() [0x00007000003ec000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076ad94fa0> (a java.lang.Thread) at java.lang.Thread.join(Thread.java:1252) - locked <0x000000076ad94fa0> (a java.lang.Thread) at java.lang.Thread.join(Thread.java:1326) at com.meituan.java8.thread.TestJoin.main(TestJoin.java:20) Locked ownable synchronizers: - None
咱們對代碼作稍稍改動,能夠驗證sleep後的線程在什麼狀態
Thread joinThread = new Thread(new Runnable() { @Override public void run() { try { TimeUnit.MINUTES.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } }, "sleeping thread");
"sleeping thread" #10 prio=5 os_prio=31 tid=0x00007f92620bc000 nid=0x5503 waiting on condition [0x00007000077b7000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.meituan.java8.thread.TestJoin$1.run(TestJoin.java:16) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None
https://javaconceptoftheday.c...
A thread enters into WAITING state when it calls wait() or join() method on an object. Before entering into WAITING state, thread releases the lock of the object it holds. It will remain in WAITING state until any other thread calls either notify() or notifyAll() on the same object.
Once the other thread calls notify() or notifyAll() on the same object, one or all the threads which are WAITING for lock of that object will be notified. All the notified threads will not get the object lock immediately. They will get the object lock on a priority basis once the current thread releases the lock. Until that they will be in BLOCKED state.
In simple terms, a thread will be in WAITING state if it is waiting for notification from other threads. A thread will be in BLOCKED state if it is waiting for other thread to release the lock it wants.
https://stackoverflow.com/que...
The difference is relatively simple.
In the BLOCKED state, a thread is about to enter a synchronized block, but there is another thread currently running inside a synchronized block on the same object. The first thread must then wait for the second thread to exit its block.
In the WAITING state, a thread is waiting for a signal from another thread. This happens typically by calling Object.wait(), or Thread.join(). The thread will then remain in this state until another thread calls Object.notify(), or dies.
https://stackoverflow.com/a/3...
wait()
wait() method releases the lock.
wait() is the method of Object class.
wait() is the non-static method - public final void wait() throws InterruptedException { //...}
wait() should be notified by notify() or notifyAll() methods.
wait() method needs to be called from a loop in order to deal with false alarm.
wait() method must be called from synchronized context (i.e. synchronized method or block), otherwise it will throw IllegalMonitorStateException
sleep()
sleep() method doesn't release the lock.
sleep() is the method of java.lang.Thread class.
sleep() is the static method - public static void sleep(long millis, int nanos) throws InterruptedException { //... }
after the specified amount of time, sleep() is completed.
sleep() better not to call from loop(i.e. see code below).
sleep() may be called from anywhere. there is no specific requirement.
4.《Effective Java》第10章