Java多線程中sleep和wait區(qū)別
前言
sleep(休眠) 和 wait(等待) 方法是 Java 多線程中常用的兩個(gè)方法,它們有什么區(qū)別及一些該注意的地方有哪些呢?下面給大家一一分解。
sleep和wait方法都是native關(guān)鍵字修飾的方法,這說明這兩個(gè)方法是原生函數(shù),也就是由C/C++實(shí)現(xiàn)的,那么我們就暫時(shí)不關(guān)心它的具體實(shí)現(xiàn)了。
sleep方法是Thread類中的方法,而wait方法是Object中的方法,那么我們首先看看wait方法。
wait()
從Object源碼中,我們可以發(fā)現(xiàn),wait有三個(gè)重載方法,分別是無參的wait方法,帶有l(wèi)ong和int類型參數(shù)的的wait方法,以及帶有l(wèi)ong類型參數(shù)的方法。其實(shí)前兩個(gè)方法最終都是調(diào)用了wait(long)方法,而wait(long)方法是native修飾的方法,它底層就是由C++實(shí)現(xiàn)的,這里暫且不討論,我們來看看這個(gè)方法的注釋。
/* wait方法會(huì)導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用notify和notifyAll方法,或者達(dá)到了指定的等待時(shí)間 使用wait方法的前提是當(dāng)前線程擁有該對(duì)象的監(jiān)視器也就是鎖 該方法會(huì)導(dǎo)致當(dāng)前線程T(調(diào)用wait方法的線程)將自己放到該對(duì)象的等待集合中,然后會(huì)放棄此對(duì)象上的所有同步聲明,也就是會(huì)放棄對(duì)象的鎖 該等待線程不會(huì)被調(diào)度并且處于休眠狀態(tài),直到以下四種情況之一發(fā)生: 1、其他線程調(diào)用等待對(duì)象notify方法,并且當(dāng)前線程T被隨機(jī)選為要喚醒的方法時(shí),線程將會(huì)退出休眠狀態(tài) 2、其他線程調(diào)用等待對(duì)象的notifyAll方法 3、其他線程中斷當(dāng)前線程T 4、超過指定的等待時(shí)間。如果等待時(shí)間為0的話,時(shí)間因素將不會(huì)被考慮,那線程將等待直到被通知喚醒 當(dāng)發(fā)生上述四種情況時(shí),線程T將會(huì)從該對(duì)象的等待集合中移除,并且可以重新被調(diào)度。然后它以通常的方式與其他線程競(jìng)爭(zhēng)對(duì)象上的同步鎖,一旦它獲得了對(duì)對(duì)象的控制權(quán),它對(duì)對(duì)象的所有同步聲明將會(huì)恢復(fù)到原來的狀態(tài),也就是說,恢復(fù)到調(diào)用wait方法時(shí)的狀態(tài)。然后線程T將會(huì)從調(diào)用wait方法的方法中返回。因此,從wait方法返回時(shí),對(duì)象和線程T的同步狀態(tài)與調(diào)用wait方法時(shí)的狀態(tài)完全相同。 如果當(dāng)前線程在等待之前或者等待時(shí)被中斷,會(huì)拋出InterruptedException異常。 注意,等待方法將當(dāng)前線程防止到該對(duì)象的等待集合時(shí),只解鎖此對(duì)象;在線程等待時(shí),當(dāng)前線程同步的其他任何對(duì)象都將保持鎖定狀態(tài)。 wait方法僅能被持有對(duì)象監(jiān)視器的線程調(diào)用(對(duì)象監(jiān)視器就相當(dāng)于對(duì)象的鎖) 通過如下方法可以獲得對(duì)象的監(jiān)視器: 1、通過執(zhí)行該對(duì)象的同步方法(也就是synchronized關(guān)鍵字修飾的方法) 2、通過執(zhí)行該對(duì)象的同步代碼塊(synchronized(Object) {}) 3、通過執(zhí)行類的同步靜態(tài)代碼塊(也就是synchronized關(guān)鍵字修飾的靜態(tài)方法) */ public final native void wait(long timeout) throws InterruptedException;
通過wait方法的注釋,我們可以發(fā)現(xiàn),wait方法有如下作用:
- 使線程進(jìn)入休眠狀態(tài),不被調(diào)度,直到被notify方法選中或者notifyAll方法的執(zhí)行,才會(huì)被喚醒
- 線程會(huì)釋放調(diào)用wait方法的對(duì)象的鎖(但是不會(huì)釋放線程持有的其他對(duì)象的鎖),這樣其他線程可以競(jìng)爭(zhēng)該對(duì)象的鎖
- 從wait方法中退出后,線程會(huì)回到調(diào)用該方法時(shí)的狀態(tài)
既然要notify和notifyAll方法才能喚醒調(diào)用wait方法陷入等待的線程,那么我們看看這兩個(gè)方法的注釋:
/* 喚醒一個(gè)等待對(duì)象鎖的線程 如果有多個(gè)線程在等待該對(duì)象,會(huì)隨機(jī)喚醒一個(gè)線程 在當(dāng)前線程放棄該對(duì)象的鎖之前,喚醒的線程將無法繼續(xù)執(zhí)行 喚醒的線程將以通常的方式與其他線程競(jìng)爭(zhēng),這些線程會(huì)公平地在這個(gè)對(duì)象上進(jìn)行同步競(jìng)爭(zhēng)。例如,被喚醒的線程在競(jìng)爭(zhēng)對(duì)象的鎖時(shí)沒有特權(quán)或者缺點(diǎn) 該方法僅在線程持有對(duì)象的監(jiān)視器時(shí)才能被調(diào)用,獲取對(duì)象的監(jiān)視器有如下方法:也就是synchronized關(guān)鍵字修飾的方法、靜態(tài)方法以及代碼塊等 */ public final native void notify(); /* 喚醒所有等待該對(duì)象監(jiān)視器的線程 在當(dāng)前持有對(duì)象鎖的線程放棄對(duì)象鎖之前,被喚醒的線程無法執(zhí)行 */ public final native void notifyAll();
通過方法的注釋來看,這兩個(gè)方法就是用于喚醒等待該對(duì)象的線程,notify隨機(jī)喚醒一個(gè)線程,notifyAll會(huì)喚醒全部線程,這些被喚醒的線程處于就緒態(tài),它們會(huì)和正在運(yùn)行的線程一起搶占對(duì)象的鎖,得到對(duì)象的鎖之后才能繼續(xù)執(zhí)行。
通過JDK源碼的注釋,我們對(duì)wait和notify方法有了更進(jìn)一步的了解,那么接著看看sleep方法,才能知道wait和sleep的區(qū)別
sleep()
Thread類中的sleep方法也有兩個(gè)重載方法,其中sleep(long)是底層的實(shí)現(xiàn)。
來看看它們的注釋:
/* 根據(jù)系統(tǒng)計(jì)時(shí)器和調(diào)度程序的精度和準(zhǔn)確性,使當(dāng)前執(zhí)行的線程休眠(暫時(shí)停止執(zhí)行)指定的毫秒數(shù) 線程不會(huì)釋放持有的鎖 該方法響應(yīng)中斷,當(dāng)遇到中斷時(shí)會(huì)拋出InterruptedException異常,并且會(huì)清除當(dāng)前線程的中斷狀態(tài) */ public static native void sleep(long millis) throws InterruptedException; /* 使當(dāng)前執(zhí)行的線程休眠(臨時(shí)停止執(zhí)行)指定的毫秒數(shù)加上指定的納秒數(shù),具體取決于系統(tǒng)計(jì)時(shí)器和調(diào)度程序的精度和準(zhǔn)確性 線程不會(huì)釋放持有的鎖 nanos的范圍:0-999999 */ public static void sleep(long millis, int nanos) ? ? throws InterruptedException { ? ? ?? ?// 判斷millis和nanos是否符合條件 ? ? ? ? if (millis < 0) { ? ? ? ? ? ? throw new IllegalArgumentException("timeout value is negative"); ? ? ? ? } ? ? ? ? if (nanos < 0 || nanos > 999999) { ? ? ? ? ? ? throw new IllegalArgumentException( ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "nanosecond timeout value out of range"); ? ? ? ? } ? ? ? ? if (nanos >= 500000 || (nanos != 0 && millis == 0)) { ? ? ? ? ? ? millis++; ? ? ? ? } ?? ??? ?// 調(diào)用原生sleep方法 ? ? ? ? sleep(millis); ? ? }
wait()和sleep()方法區(qū)別
區(qū)別1:使用限制
使用 sleep 方法可以讓讓當(dāng)前線程休眠,時(shí)間一到當(dāng)前線程繼續(xù)往下執(zhí)行,在任何地方都能使用,但需要捕獲 InterruptedException 異常。
try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); }
而使用 wait 方法則必須放在 synchronized 塊里面,同樣需要捕獲 InterruptedException 異常,并且需要獲取對(duì)象的鎖。
synchronized (lock){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
而且 wait 還需要額外的方法 notify/ notifyAll 進(jìn)行喚醒,它們同樣需要放在 synchronized 塊里面,且獲取對(duì)象的鎖。。
synchronized (lock) { ? ? // 隨機(jī)喚醒 ? ? lock.notify(); ? ? // 喚醒全部 ? ? lock.notifyAll(); }
當(dāng)然也可以使用帶時(shí)間的 wait(long millis) 方法,時(shí)間一到,無需其他線程喚醒,也會(huì)重新競(jìng)爭(zhēng)獲取對(duì)象的鎖繼續(xù)執(zhí)行。
區(qū)別2:使用場(chǎng)景
sleep 一般用于當(dāng)前線程休眠,或者輪循暫停操作,wait 則多用于多線程之間的通信。
區(qū)別3:所屬類
sleep 是 Thread 類的靜態(tài)本地方法,wait 則是 Object 類的本地方法。
java.lang.Thread#sleep
public static native void sleep(long millis) throws InterruptedException;
java.lang.Object#wait
public final native void wait(long timeout) throws InterruptedException;
為什么要這樣設(shè)計(jì)呢?
因?yàn)?sleep 是讓當(dāng)前線程休眠,不涉及到對(duì)象類,也不需要獲得對(duì)象的鎖,所以是線程類的方法。wait 是讓獲得對(duì)象鎖的線程實(shí)現(xiàn)等待,前提是要楚獲得對(duì)象的鎖,所以是類的方法。
區(qū)別4:釋放鎖
Object lock = new Object(); synchronized (lock) { try { lock.wait(3000L); Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } }
如上代碼所示,wait 可以釋放當(dāng)前線程對(duì) lock 對(duì)象鎖的持有,而 sleep 則不會(huì)。
區(qū)別5:線程切換
sleep 會(huì)讓出 CPU 執(zhí)行時(shí)間且強(qiáng)制上下文切換,而 wait 則不一定,wait 后可能還是有機(jī)會(huì)重新競(jìng)爭(zhēng)到鎖繼續(xù)執(zhí)行的。
到此這篇關(guān)于Java多線程中sleep和 wait區(qū)別的文章就介紹到這了,更多相關(guān)Java sleep和wait內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea的spring boot項(xiàng)目實(shí)現(xiàn)更改端口號(hào)操作
這篇文章主要介紹了idea的spring boot項(xiàng)目實(shí)現(xiàn)更改端口號(hào)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09Spring Boot集成Redis實(shí)現(xiàn)緩存機(jī)制(從零開始學(xué)Spring Boot)
這篇文章主要介紹了Spring Boot集成Redis實(shí)現(xiàn)緩存機(jī)制(從零開始學(xué)Spring Boot),需要的朋友可以參考下2017-04-04Java編程中應(yīng)用的GUI設(shè)計(jì)基礎(chǔ)
這篇文章主要介紹了Java編程中應(yīng)用的GUI設(shè)計(jì)基礎(chǔ),為一些Java開發(fā)CS類型應(yīng)用的基礎(chǔ)概念知識(shí),需要的朋友可以參考下2015-10-10IntelliJ IDEA 2019.3激活破解的詳細(xì)方法(親測(cè)有效,可激活至 2089&
本教程適用于 JetBrains 全系列產(chǎn)品,包括 Pycharm、IDEA、WebStorm、Phpstorm、Datagrip、RubyMine、CLion、AppCode 等,本教程無需修改 hosts 文件,對(duì)IntelliJ IDEA 2019.3激活破解的詳細(xì)方法的相關(guān)知識(shí)感興趣的朋友一起看看吧2020-09-09SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié)
本文主要介紹了SpringSecurity的TokenStore四種實(shí)現(xiàn)方式小結(jié),分別是InMemoryTokenStore,JdbcTokenStore,JwkTokenStore,RedisTokenStore,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01解決IDEA的maven項(xiàng)目中沒有新建Servlet文件的選項(xiàng)問題
這篇文章主要介紹了IDEA的maven項(xiàng)目中沒有新建Servlet文件的選項(xiàng)問題及解決方法,本文給大家分享問題原因就解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09springboot集成shiro權(quán)限管理簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了springboot集成shiro權(quán)限管理簡(jiǎn)單實(shí)現(xiàn),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08