各位看官,大家早安午安晚安呀~~~
如果您覺得這篇文章對您有幫助的話
歡迎您一鍵三連,小編盡全力做到更好
歡迎您分享給更多人哦
今天我們來學習:wait和notify : 避免線程餓死(以及votile內存可見性和指令重排序問題)
目錄
4.volatile
4.1.保證內存可見性
4.2.:指令重排序問題(我們放到下一篇博客來講解)
5.wait,notify
5.1.wait方法的使用
5.2.notify方法的使用
6.wait和notify可以避免線程餓死
4.volatile
volatile的兩個作用(其實都是不讓編譯器做出多余的優化,對我們的程序產生bug)
1.保證內存可見性
2.解決指令重排序問題
4.1.保證內存可見性
序言:
計算機運行的程序往往要訪問數據,這些依賴的數據一般都被讀取到了內存里面(譬如我們定義的一個變量就是在內存里面)
就譬如count++;cpu先把count讀到cpu寄存器里面再進行++;
但是!? cpu其他的操作都很快,但是讀內存就比較慢(讀硬盤更慢,快慢都是相對的)
因此,為了提高效率編譯器就會做出優化,把一些要讀內存的操作優化成讀寄存器。減少讀內存的次數,就提高了整體程序的效率~~
public class Demo1 {private static int isQuit = 0;public static void main(String[] args) {Thread t1 = new Thread(() ->{while(isQuit == 0){// 一直循環啥也不不干}System.out.println("t1 退出");});Thread t2 = new Thread(()->{System.out.println(" 請輸入isQuit的值");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt(); // 輸入不為0的值,使t1線程結束});t1.start();t2.start();}
}
我們發現我們明明輸入非0值但是t1線程就是沒結束為什么呢?
所以說volatile就是一個優化方法,我們給isQuit前面用volatile修飾就可以避免這樣的問題了
? ? ? ?在多線程環境下,編譯器對于是否要進行這樣的優化,判定不一定準.這個時候就需要程序猿通過 volatile 關鍵字,告訴編譯器,這個情況不用優化!!!(優化,是算的快了,但是算的不準了)
?總之:編譯器,也不是萬能的.也會有一些自己短板的地方.此時就需要程序猿進行補充了.
只需要給 isQuit加上 volatile 關鍵字修飾,此時編譯器自然就會禁止上述優化過程。
還有另一種方式,也可以避免這樣的問題(再加上一個時間更長的操作,讓讀內存不那么慢了(相對來說)哈哈)
public class Demo1 {private static int isQuit = 0;public static void main(String[] args) {Thread t1 = new Thread(() ->{while(isQuit == 0){// 一直循環啥也不不干try { //就多加了一個sleepThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 退出");});Thread t2 = new Thread(()->{System.out.println(" 請輸入isQuit的值");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt(); // 輸入不為0的值,使t1線程結束});t1.start();t2.start();}
}
此時沒加 volatile, 但是給循環里加了個 slee
線程是可以退出了
加了 sleep 之后, while 循環執行速度就慢了.由于次數少了,load (讀內存)操作的開銷,就不大了.因此,優化也就沒必要進行了.
沒有觸發 load的優化,也就沒有觸發內存可見性問題了.
到底啥時候代碼有優化,啥時候沒有?也說不清楚,但是使用 volatile 還是更靠譜的選擇!!
4.2.:指令重排序問題(我們放到下一篇博客來講解)
5.wait,notify
每個線程都是獨立的執行流
想法:
由于線程之間是搶占式執行的, 因此線程之間執行的先后順序難以預知.(join是決定了線程結束的先后順序)
但是實際開發中有時候我們希望合理的協調多個線程之間的執行先后順序.
完成這個協調工作, 主要涉及到三個方法
wait() / wait(long timeout): 讓當前線程進入等待狀態.
notify() / notifyAll(): 喚醒在當前對象上等待的線程.
注意: wait, notify, notifyAll 都是 Object 類的方法.
5.1.wait方法的使用
wait()方法要做的事情:
- 釋放當前的鎖(前提是你得有鎖,不然怎么釋放)
- 線程進入阻塞
- 線程被喚醒, 重新嘗試獲取這個鎖.
wait 要搭配 synchronized 來使用. 脫離 synchronized 使用 wait 會直接拋出異常.
public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("wait 之前");locker.wait(); //沒加鎖,會拋出異常System.out.println("wait 之后");}
wait 結束等待的條件:
1.其他線程調用該對象的 notify 方法.
2.wait 等待時間超時 (wait 方法提供一個帶有 timeout 參數的版本, 來指定等待時間).
3.其他線程調用該等待線程的 interrupted 方法, 導致 wait 拋出 InterruptedException 異常.
接下來我們看一個代碼
public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("wait 之前");locker.wait(); // 只要一般涉及阻塞的方法都會拋出異常//locker這個對象只要不notify,這個線程就一直處于waiting狀態(加了等待時間就不會了)System.out.println("wait 之后");}
運行結果以及jconsole觀察到的結果(這個程序就一直等待,死等(除非有等待時間))
5.2.notify方法的使用
我們以代碼舉例:創建三個線程都去wait,主線程notify喚醒(隨機的)
public static void main(String[] args) throws InterruptedException {Object locker = new Object(); // 公用一個鎖對象Thread t1 = new Thread(() ->{synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 醒了");});Thread t2 = new Thread(()-> {synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2 醒了");});Thread t3 = new Thread(()->{synchronized(locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2 醒了");});t1.start();t2.start();t3.start();System.out.println("喚醒其中一個線程");Thread.sleep(1000); // 確保其他線程已經處于waiting狀態synchronized(locker){locker.notify();// 也要記得加鎖呀,不然咋釋放鎖}}
結果:可以看到隨機一個線程被喚醒,其他線程在waiting狀態
如果是notifyAll方法呢?(三個線程都被喚醒了,但是第一個拿到鎖的線程如果執行的任務比較多,另外兩個線程會處于BLOCKED狀態(由于鎖競爭導致的阻塞))
6.wait和notify可以避免線程餓死
先介紹synchronized是可重入鎖,什么是可重入鎖呢?一個線程對一個對象連續加鎖兩次不會出現死鎖。就譬如這個按理說會出現死鎖但是我是可重入的所以不會形成死鎖
說到死鎖:舉一個例子()(這樣一個線程想要多把鎖,進而形成環,直接卡死就會導致死鎖)
說到環就想到哲學家問題
總結:四個成因(破壞一個就解除)
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance(){ // 一定要是靜態方法,不然一開始別人都拿不到這個對象,又沒辦法調用這個方法,豈不是貽笑大方return instance;}private Singleton(){} // 啥也不用干,也干了呀,把構造方法給藏起來了
}public class SingletonDemo{public static void main(String[] args) {Singleton singleton = new Singleton();}
}
上述就是wait和notify : 避免線程餓死(以及votile內存可見性和指令重排序問題)的全部內容啦
能看到這里相信您一定對小編的文章有了一定的認可。
有什么問題歡迎各位大佬指出
歡迎各位大佬評論區留言修正~~