wait放弃对象锁_终于搞懂了sleep/wait/notify/notifyAll,真的是不容易
sleep/wait/notify/notifyAll分別有什么作用?它們的區(qū)別是什么?wait時(shí)為什么要放在循環(huán)里而不能直接用if?
簡(jiǎn)介
首先對(duì)幾個(gè)相關(guān)的方法做個(gè)簡(jiǎn)單解釋,Object中有幾個(gè)用于線程同步的方法:wait、notify、notifyAll。
public class Object { public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll();}- wait: 釋放當(dāng)前鎖,阻塞直到被notify或notifyAll喚醒,或者超時(shí),或者線程被中斷(InterruptedException)
- notify: 任意選擇一個(gè)(無法控制選哪個(gè))正在這個(gè)對(duì)象上等待的線程把它喚醒,其它線程依然在等待被喚醒
- notifyAll: 喚醒所有線程,讓它們?nèi)ジ?jìng)爭(zhēng),不過也只有一個(gè)能搶到鎖
- sleep: 不是Object中的方法,而是Thread類的靜態(tài)方法,讓當(dāng)前線程持有鎖阻塞指定時(shí)間
sleep和wait
sleep和wait都可以讓線程阻塞,也都可以指定超時(shí)時(shí)間,甚至還都會(huì)拋出中斷異常InterruptedException。
而它們最大的區(qū)別就在于,sleep時(shí)線程依然持有鎖,別人無法進(jìn)當(dāng)前同步方法;wait時(shí)放棄了持有的鎖,其它線程有機(jī)會(huì)進(jìn)入該同步方法。多次提到同步方法,因?yàn)閣ait必須在synchronized同步代碼塊中,否則會(huì)拋出異常IllegalMonitorStateException,notify也是如此,可以說wait和notify是就是為了在同步代碼中做線程調(diào)度而生的。
下面一個(gè)簡(jiǎn)單的例子展現(xiàn)sleep和wait的區(qū)別:
import java.util.Date;import java.util.concurrent.atomic.AtomicInteger;public class Main { // 日志行號(hào)記錄 private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 開啟兩個(gè)線程去執(zhí)行test方法 new Thread(main::test).start(); new Thread(main::test).start(); } private synchronized void test() { try { log("進(jìn)入了同步方法,并開始睡覺,1s"); // sleep不會(huì)釋放鎖,因此其他線程不能進(jìn)入這個(gè)方法 Thread.sleep(1000); log("睡好了,但沒事做,有事叫我,等待2s"); //阻塞在此,并且釋放鎖,其它線程可以進(jìn)入這個(gè)方法 //當(dāng)其它線程調(diào)用此對(duì)象的notify或者notifyAll時(shí)才有機(jī)會(huì)停止阻塞 //就算沒有人notify,如果超時(shí)了也會(huì)停止阻塞 wait(2000); log("我要走了,但我要再睡一覺,10s"); //這里睡的時(shí)間很長(zhǎng),因?yàn)闆]有釋放鎖,其它線程就算wait超時(shí)了也無法繼續(xù)執(zhí)行 Thread.sleep(10000); log("走了"); notify(); } catch (InterruptedException e) { e.printStackTrace(); } } // 打印日志 private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 輸出:1 00:13:23Thread-0 進(jìn)入了同步方法,并開始睡覺,1s2 00:13:24Thread-0 睡好了,但沒事做,有事叫我,等待2s3 00:13:24Thread-1 進(jìn)入了同步方法,并開始睡覺,1s4 00:13:25Thread-1 睡好了,但沒事做,有事叫我,等待2s5 00:13:26Thread-0 我要走了,但我要再睡一覺,10s6 00:13:36Thread-0 走了7 00:13:36Thread-1 我要走了,但我要再睡一覺,10s8 00:13:46Thread-1 走了*/對(duì)輸出做個(gè)簡(jiǎn)單解釋(已經(jīng)看懂代碼的童鞋可以跳過):
1 00:13:23Thread-0 進(jìn)入了同步方法,并開始睡覺,1s // Thread-0首先進(jìn)入同步方法,Thread-1只能門外候著2 00:13:24Thread-0 睡好了,但沒事做,有事叫我,等待2s // Thread-0 sleep 1秒這段時(shí)間,Thread-1沒進(jìn)來,證明sleep沒有釋放鎖3 00:13:24Thread-1 進(jìn)入了同步方法,并開始睡覺,1s // Thread-0開始wait后Thread-1馬上就進(jìn)來了,證明wait釋放了鎖4 00:13:25Thread-1 睡好了,但沒事做,有事叫我,等待2s // Thread-1也打算wait 2秒(2秒后真的能醒來嗎?)5 00:13:26Thread-0 我要走了,但我要再睡一覺,10s // Thread-0已經(jīng)wait超時(shí)醒來了,這次準(zhǔn)備sleep 10s6 00:13:36Thread-0 走了 // 10s過去了Thread-0都sleep結(jié)束了,那個(gè)說要wait 2s的Thread-1還沒動(dòng)靜,證明超時(shí)也沒用,還得搶到鎖7 00:13:36Thread-1 我要走了,但我要再睡一覺,10s // Thread-0退出同步代碼后,Thread-1才終于得到了鎖,能行動(dòng)了8 00:13:46Thread-1 走了notify和notifyAll
同樣是喚醒等待的線程,同樣最多只有一個(gè)線程能獲得鎖,同樣不能控制哪個(gè)線程獲得鎖。
區(qū)別在于:
- notify:喚醒一個(gè)線程,其他線程依然處于wait的等待喚醒狀態(tài),如果被喚醒的線程結(jié)束時(shí)沒調(diào)用notify,其他線程就永遠(yuǎn)沒人去喚醒,只能等待超時(shí),或者被中斷
- notifyAll:所有線程退出wait的狀態(tài),開始競(jìng)爭(zhēng)鎖,但只有一個(gè)線程能搶到,這個(gè)線程執(zhí)行完后,其他線程又會(huì)有一個(gè)幸運(yùn)兒脫穎而出得到鎖
如果覺得解釋的不夠明白,代碼來一波:
import java.util.Date;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.atomic.AtomicInteger;public class Main { private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 開啟兩個(gè)線程去執(zhí)行test方法 for (int i = 0; i < 10; i++) { new Thread(main::testWait).start(); } Thread.sleep(1000); for (int i = 0; i < 5; i++) { main.testNotify(); } } private synchronized void testWait() { try { log("進(jìn)入了同步方法,開始wait"); wait(); log("wait結(jié)束"); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void testNotify() { notify(); } private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 輸出:1 00:59:32Thread-0 進(jìn)入了同步方法,開始wait2 00:59:32Thread-9 進(jìn)入了同步方法,開始wait3 00:59:32Thread-8 進(jìn)入了同步方法,開始wait4 00:59:32Thread-7 進(jìn)入了同步方法,開始wait5 00:59:32Thread-6 進(jìn)入了同步方法,開始wait6 00:59:32Thread-5 進(jìn)入了同步方法,開始wait7 00:59:32Thread-4 進(jìn)入了同步方法,開始wait8 00:59:32Thread-3 進(jìn)入了同步方法,開始wait9 00:59:32Thread-2 進(jìn)入了同步方法,開始wait10 00:59:32Thread-1 進(jìn)入了同步方法,開始wait11 00:59:33Thread-0 wait結(jié)束12 00:59:33Thread-6 wait結(jié)束13 00:59:33Thread-7 wait結(jié)束14 00:59:33Thread-8 wait結(jié)束15 00:59:33Thread-9 wait結(jié)束*/例子中有10個(gè)線程在wait,但notify了5次,然后其它線程一直阻塞,這也就說明使用notify時(shí)如果不能準(zhǔn)確控制和wait的線程數(shù)對(duì)應(yīng),可能會(huì)導(dǎo)致某些線程永遠(yuǎn)阻塞。
使用notifyAll喚醒所有等待的線程:
import java.util.Date;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.atomic.AtomicInteger;public class Main { private AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Main main = new Main(); // 開啟兩個(gè)線程去執(zhí)行test方法 for (int i = 0; i < 5; i++) { new Thread(main::testWait).start(); } Thread.sleep(1000); main.testNotifyAll(); } private synchronized void testWait() { try { log("進(jìn)入了同步方法,開始wait"); wait(); log("wait結(jié)束"); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void testNotifyAll() { notifyAll(); } private void log(String s) { System.out.println(count.incrementAndGet() + " " + new Date().toString().split(" ")[3] + "" + Thread.currentThread().getName() + " " + s); }}/* 輸出:1 01:03:24Thread-0 進(jìn)入了同步方法,開始wait2 01:03:24Thread-4 進(jìn)入了同步方法,開始wait3 01:03:24Thread-3 進(jìn)入了同步方法,開始wait4 01:03:24Thread-2 進(jìn)入了同步方法,開始wait5 01:03:24Thread-1 進(jìn)入了同步方法,開始wait6 01:03:25Thread-1 wait結(jié)束7 01:03:25Thread-2 wait結(jié)束8 01:03:25Thread-3 wait結(jié)束9 01:03:25Thread-4 wait結(jié)束10 01:03:25Thread-0 wait結(jié)束*/只需要調(diào)用一次notifyAll,所有的等待線程都被喚醒,并且去競(jìng)爭(zhēng)鎖,然后依次(無序)獲取鎖完成了后續(xù)任務(wù)。
為什么wait要放到循環(huán)中使用
一些源碼中出現(xiàn)wait時(shí),往往都是伴隨著一個(gè)循環(huán)語(yǔ)句出現(xiàn)的,比如:
private synchronized void f() throws InterruptedException { while (!isOk()) { wait(); } System.out.println("I'm ok");}既然wait會(huì)被阻塞直到被喚醒,那么用if+wait不就可以了嗎?其他線程發(fā)現(xiàn)條件達(dá)到時(shí)notify一下不就行了?
理想情況確實(shí)如此,但實(shí)際開發(fā)中我們往往不能保證這個(gè)線程被notify時(shí)條件已經(jīng)滿足了,因?yàn)楹芸赡苡心硞€(gè)無關(guān)(和這個(gè)條件的邏輯無關(guān))的線程因?yàn)樾枰€程調(diào)度而調(diào)用了notify或者notifyAll。此時(shí)如果樣例中位置等待的線程不巧被喚醒,它就會(huì)繼續(xù)往下執(zhí)行,但因?yàn)橛玫膇f,這次被喚醒就不會(huì)再判斷條件是否滿足,最終程序按照我們不期望的方式執(zhí)行下去。
總結(jié)
以上是生活随笔為你收集整理的wait放弃对象锁_终于搞懂了sleep/wait/notify/notifyAll,真的是不容易的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《新·福音战士剧场版:终》国内海报抄袭!
- 下一篇: access找不到输入表或者dual_在