同步問題中,一個很重要的問題是同步的域,什么是同步的域呢?簡單以 synchronized 這個關鍵字來說,就是它所同步的范圍。並發編程中很多時候出現的問題沒有選好同步范圍所導致的。但現有的同步關鍵字synchronized所能體現出來的對域的控制,估計用過的的人都應該感覺到並不是很理想。這個時候是不是很懷念Windows下所提供的Mutex操作,通過申請和釋放的函數的位置控制同步的域,用起來要方便一些。
現在為了方便我們自己也可以做一個類似Windows的Mutex的類來方便我們對域的控制,下面看這個類的制作過程。
和制作上一個類一樣,我們先來說一下希望這個類完成的功能:
1.調用getMutexFlag(),僅能是並發的線程中的一個繼續執行,其余的阻塞。
2.調用freeMutexFlag(),執行的線程離開同步塊(臨界區),並能從阻塞線程中釋放一個線程從getMutexFlag()中返回。
3.getMutexFlag()、freeMutexFlag(),都必須是原子操作。
上面這三個條件其實就是對Windows下Mutex的要求。
一開始我們也許會寫出這樣的代碼:
1 package com.choi; 2 3 public class MutexFlag { 4 5 protected Thread currentThread = null; 6 7 public synchronized void getMutexFalg() { 8 if (currentThread == null) { 9 currentThread = Thread.currentThread(); 10 } 11 while (currentThread != Thread.currentThread()) { 12 try { 13 wait(); 14 if (currentThread == null) { 15 currentThread = Thread.currentThread(); 16 } 17 } catch (Exception e) { 18 } 19 } 20 } 21 22 public synchronized void freeMutexFlag(){ 23 if(currentThread!=null){ 24 currentThread = null; 25 try { 26 notify(); 27 } catch (Exception e) { 28 } 29 } 30 } 31 }
這段代碼看起來是沒有問題的,其實這段代碼運行起來確實是沒有問題的,但我們能把它提供給我們的項目組用嗎?答案是不能的,我們來考慮一種情況:
項目組有三個程序員A,B,C。 A用這個類寫了一個函數funA(get到Mutex未釋放),B用這個類寫了一個函數funB(get到Mutex未釋放),C調用了funA,funB,用在一個線程中了。則這個線程運行到funA時得到了Mutex,運行到funB時再去申請Mutex能申請到嗎?通過分析代碼執行,肯定是NO。並申請不到,因為第一個還沒有釋放。Java里面如果是兩個synchronized嵌套,則沒有問題,第一個得到鎖后,第二個如申請的是同一個鎖,就會自動判斷為已申請到。
我們可以用一個變量來是我們的MutexFlag實現synchronized的嵌套解決方案:
1 package com.choi; 2 3 public class MutexFlag { 4 5 protected Thread currentThread = null; 6 protected int count = 0; 7 8 public synchronized void getMutexFalg() { 9 while (tryGetMutexFlag() == false) { 10 try { 11 wait(); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 18 public synchronized boolean tryGetMutexFlag() { 19 if (currentThread == null) { 20 currentThread = Thread.currentThread(); 21 count = 1; 22 return true; 23 } 24 if (currentThread == Thread.currentThread()) { 25 count++; 26 return true; 27 } 28 return false; 29 } 30 31 public synchronized void freeMutexFlag() { 32 if (currentThread == Thread.currentThread()) { 33 count--; 34 } 35 if (count == 0) { 36 currentThread = null; 37 notify(); 38 } 39 } 40 }
為了編寫方便我把判斷獲取MutexFlag的代碼寫到了同步的tryGetMutexFlag中了,沒申請成功一次就對count做加一操作,每一free都對count做減一操作,那么當count從新等於零的時候我們就去釋放一個線程獲取MutexFlag。
哦,有人說如果沒有調用get就調用free會有問題,這里說明一下沒有問題的,因為如果沒有get,currentThread是等於null的,所以count==0,然后就會調用notify,其實沒有問題啦,測試發現,notify不會拋出人異常,相當於空操作。
歡迎到我的GitHub主頁獲取代碼:https://github.com/choitony/Java-MutexFlag-Class/tree/master