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