【JUC并发编程03】线程间通信
文章目錄
- 3 線程間通信
- 3.1 synchronized 實現案例
- 3.2 虛假喚醒問題
- 3.3 Lock 實現案例
3 線程間通信
線程間通信有兩種實現方法:
關鍵字 synchronized 與 wait()/notify() 這兩個方法一起使用可以實現等待/通知模式
Lock 接口中的 newContition() 方法返回 Condition 對象,Condition 類也可以實現等待/通知模式
用 notify()通知時,JVM 會隨機喚醒某個等待的線程
使用 Condition 類可以進行選擇性通知, Condition 比較常用的兩個方法:
1. await() 會使當前線程等待,同時會釋放鎖,當其他線程調用 signal()時,線程會重新獲得鎖并繼續執行2. signal() 用于喚醒一個等待的線程操作線程的時候,等待線程使用wait()
通知另外的線程操作用notify()、notifyAll()
假設有兩個線程,該線程在執行過程中,判斷值(不是該值等待,讓其他線程搶),操作值,通知另外一個線程的調度
3.1 synchronized 實現案例
實現兩個線程對 num 這個值操作,一個線程加1,一個線程減1,交替實現多次
// 創建一個資源類 class Share{// 設置臨界資源private int number = 0;// 實現+1操作public synchronized void incr() throws InterruptedException {// 操作:判斷、干活、通知if (number != 0) {// number不為0,等待// wait 有一個特點,在哪里睡,就在哪里醒this.wait(); }number++;System.out.print(Thread.currentThread().getName()+"::"+number);// 喚醒其他線程// 注意這里的通知是隨機的,就是只能通知全部this.notifyAll();}// 實現-1操作public synchronized void decr() throws InterruptedException {// 操作:判斷、干活、通知if (number != 1) {// number不為0,等待this.wait();}number--;System.out.println(Thread.currentThread().getName()+"::"+number);this.notifyAll();} } public class InterThreadCommunication {public static void main(String[] args) {Share share = new Share();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.incr();}} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.decr();}} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();} }輸出結果如下
3.2 虛假喚醒問題
虛假喚醒主要出現在多線程中出現。
同樣使用上述案例,現在有四個線程,分別為A,B,C,D,其中A,C線程做+1操作,B,D線程做-1操作,想要的結尾應該是A,C線程輸出值為1,B,D線程輸出值為0 。修改上述代碼如下:
// 創建一個資源類 class Share{// 設置臨界資源private int number = 0;// 實現+1操作public synchronized void incr() throws InterruptedException {// 操作:判斷、干活、通知if (number != 0) {// number不為0,等待this.wait();}number++;System.out.print(Thread.currentThread().getName()+"::"+number+"--->");// 喚醒其他線程this.notifyAll();}// 實現-1操作public synchronized void decr() throws InterruptedException {// 操作:判斷、干活、通知if (number != 1) {// number不為0,等待this.wait();}number--;System.out.println(Thread.currentThread().getName()+"::"+number);this.notifyAll();} } public class InterThreadCommunication {public static void main(String[] args) {Share share = new Share();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.incr();}} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.decr();}} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.incr();}} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {share.decr();}} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();} }輸出結尾如下,顯然最后輸出的結果和我們預想的是不一樣的。那么問題出在哪里呢?
查找 JDK1.8 文檔,在 Object 的 wait() 方法中有如下介紹
在一個參數版本中,中斷和虛假喚醒是可能的,并且該方法應該始終在循環中使用
也就是說,這種現象叫做【虛假喚醒】。所謂虛假喚醒,就是 wait()方法的一個特點,總結來說 wait() 方法使線程在哪里睡就在哪里醒。 這是什么意思呢?那就以上述代碼為例。
當 A 進入臨界區,BCD三個線程在 if 判斷后進入 wait() 等待,當A線程完成操作,此時 number 值為1,notifyAll() 會隨機喚醒一個線程。
現在C被喚醒,由于 wait() 方法使線程在哪里睡就在哪里醒,所以接下來C在執行時不會再通過 if 判斷而是直接+1,此時 number 就是2了。從而導致最后輸出的結果和我們預想的不一致。
按照 JDK1.8 文檔的提示,將資源類的 incr() 方法和 decr() 方法中的if語句改為循環語句,修改代碼如下:
// 創建一個資源類 class Share{// 設置臨界資源private int number = 0;// 實現+1操作public synchronized void incr() throws InterruptedException {// 操作:判斷、干活、通知while (number != 0) {// number不為0,等待// 哪里睡哪里起this.wait();}number++;System.out.print(Thread.currentThread().getName()+"::"+number+"--->");// 喚醒其他線程this.notifyAll();}// 實現-1操作public synchronized void decr() throws InterruptedException {// 操作:判斷、干活、通知while (number != 1) {// number不為0,等待this.wait();}number--;System.out.println(Thread.currentThread().getName()+"::"+number);this.notifyAll();} }此時輸出結果符合我們預期:
3.3 Lock 實現案例
在 Lock 接口中,有一個 newCondition() 方法,該方法返回一個新 Condition 綁定到該實例 Lock 實例。
Condition 類中有 await() 和 signalAll() 等方法,和 synchronized 實現案例中的 wait() 和 notifyAll() 方法相同。所以通過 Lock 接口創建一個 Condition 對象,由該對象的方法進行等待和喚醒操作
實例代碼如下,主要改動的是資源類,main方法中代碼不變。
class Share {// 設置臨界資源private int number = 0;// 創建一個Comprivate Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();// 實現+1操作public void incr() {// 上鎖lock.lock();try {// 判斷while (number != 0) {condition.await();}// 干活number++;System.out.print(Thread.currentThread().getName() + "::" + number + "--->");// 通知condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}// 實現-1操作public void decr() throws InterruptedException {// 上鎖lock.lock();try {// 判斷while (number != 1) {condition.await();}// 干活number--;System.out.println(Thread.currentThread().getName() + "::" + number);// 通知condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}} }測試結尾如下:
總結
以上是生活随笔為你收集整理的【JUC并发编程03】线程间通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【JUC并发编程01】JUC概述
- 下一篇: 如何把本地idea上的项目上传到gith