執行緒同步工具(一)控制併發訪問資源
宣告:本文是《 Java 7 Concurrency Cookbook 》的第三章, 作者: Javier Fernández González 譯者:鄭玉婷
控制併發訪問資源
這個指南,你將學習怎樣使用Java語言提供的Semaphore機制。Semaphore是一個控制訪問多個共享資源的計數器。
Semaphore的內容是由Edsger Dijkstra引入並在 THEOS作業系統上第一次使用。
當一個執行緒想要訪問某個共享資源,首先,它必須獲得semaphore。如果semaphore的內部計數器的值大於0,那麼semaphore減少計數器的值並允許訪問共享的資源。計數器的值大於0表示,有可以自由使用的資源,所以執行緒可以訪問並使用它們。
另一種情況,如果semaphore的計數器的值等於0,那麼semaphore讓執行緒進入休眠狀態一直到計數器大於0。計數器的值等於0表示全部的共享資源都正被執行緒們使用,所以此執行緒想要訪問就必須等到某個資源成為自由的。
當執行緒使用完共享資源時,他必須放出semaphore為了讓其他執行緒可以訪問共享資源。這個操作會增加semaphore的內部計數器的值。
在這個指南里,你將學習如何使用Semaphore類來實現一種比較特殊的semaphores種類,稱為binary semaphores。這個semaphores種類保護訪問共享資源的獨特性,所以semaphore的內部計數器的值只能是1或者0。為了展示如何使用它,你將要實現一個PrintQueue類來讓併發任務列印它們的任務。這個PrintQueue類會受到binary semaphore的保護,所以每次只能有一個執行緒可以列印。
準備
指南中的例子是使用Eclipse IDE 來實現的。如果你使用Eclipse 或者其他的IDE,例如NetBeans, 開啟並建立一個新的java任務。
怎麼做呢…
按照這些步驟來實現下面的例子:
//1. 建立一個會實現print queue的類名為 PrintQueue。 public class PrintQueue { //2. 宣告一個物件為Semaphore,稱它為semaphore。 private final Semaphore semaphore; //3. 實現類的建構函式並初始能保護print quere的訪問的semaphore物件的值。 public PrintQueue(){ semaphore=new Semaphore(1); } //4. 實現Implement the printJob()方法,此方法可以模擬列印文件,並接收document物件作為引數。 public void printJob (Object document){ //5. 在這方法內,首先,你必須呼叫acquire()方法獲得demaphore。這個方法會丟擲 InterruptedException異常,使用必須包含處理這個異常的程式碼。 try { semaphore.acquire(); //6. 然後,實現能隨機等待一段時間的模擬列印文件的行。 long duration=(long)(Math.random()*10); System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().getName(),duration); Thread.sleep(duration); //7. 最後,釋放semaphore通過呼叫semaphore的relaser()方法。 } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } //8. 建立一個名為Job的類並一定實現Runnable 介面。這個類實現把文件傳送到印表機的任務。 public class Job implements Runnable { //9. 宣告一個物件為PrintQueue,名為printQueue。 private PrintQueue printQueue; //10. 實現類的建構函式,初始化這個類裡的PrintQueue物件。 public Job(PrintQueue printQueue){ this.printQueue=printQueue; } //11. 實現方法run()。 @Override public void run() { //12. 首先, 此方法寫資訊到操控臺表明任務已經開始執行了。 System.out.printf("%s: Going to print a job\n",Thread. currentThread().getName()); //13. 然後,呼叫PrintQueue 物件的printJob()方法。 printQueue.printJob(new Object()); //14. 最後, 此方法寫資訊到操控臺表明它已經結束運行了。 System.out.printf("%s: The document has been printed\n",Thread.currentThread().getName()); } //15. 實現例子的main類,建立名為 Main的類並實現main()方法。 public class Main { public static void main (String args[]){ //16. 建立PrintQueue物件名為printQueue。 PrintQueue printQueue=new PrintQueue(); //17. 建立10個threads。每個執行緒會執行一個傳送文件到print queue的Job物件。 Thread thread[]=new Thread[10]; for (int i=0; i<10; i++){ thread[i]=new Thread(new Job(printQueue),"Thread"+i); } //18. 最後,開始這10個執行緒們。 for (int i=0; i<10; i++){ thread[i].start(); }
它是怎麼工作的…
這個例子的關鍵是PrintQueue類的printJob()方法。此方法展示了3個你必須遵守的步驟當你使用semaphore來實現critical section時,並保護共享資源的訪問:
1. 首先, 你要呼叫acquire()方法獲得semaphore。
2. 然後, 對共享資源做出必要的操作。
3. 最後, 呼叫release()方法來釋放semaphore。
另一個重點是PrintQueue類的構造方法和初始化Semaphore物件。你傳遞值1作為此構造方法的引數,那麼你就建立了一個binary semaphore。初始值為1,就保護了訪問一個共享資源,在例子中是print queue。
當你開始10個threads,當你開始10個threads時,那麼第一個獲得semaphore的得到critical section的訪問權。剩下的執行緒都會被semaphore阻塞直到那個獲得semaphore的執行緒釋放它。當這情況發生,semaphore在等待的執行緒中選擇一個並給予它訪問critical section的訪問權。全部的任務都會列印文件,只是一個接一個的執行。
更多…
Semaphore類有另2個版本的 acquire() 方法:
- acquireUninterruptibly():acquire()方法是當semaphore的內部計數器的值為0時,阻塞執行緒直到semaphore被釋放。在阻塞期間,執行緒可能會被中斷,然後此方法丟擲InterruptedException異常。而此版本的acquire方法會忽略執行緒的中斷而且不會丟擲任何異常。
- tryAcquire():此方法會嘗試獲取semaphore。如果成功,返回true。如果不成功,返回false值,並不會被阻塞和等待semaphore的釋放。接下來是你的任務用返回的值執行正確的行動。
Semaphores的公平性
fairness的內容是指全java語言的所有類中,那些可以阻塞多個執行緒並等待同步資源釋放的類(例如,semaphore)。預設情況下是非公平模式。在這個模式中,當同步資源釋放,就會從等待的執行緒中任意選擇一個獲得資源,但是這種選擇沒有任何標準。而公平模式可以改變這個行為並強制選擇等待最久時間的執行緒。
隨著其他類的出現,Semaphore類的建構函式容許第二個引數。這個引數必需是 Boolean 值。如果你給的是 false 值,那麼建立的semaphore就會在非公平模式下執行。如果你不使用這個引數,是跟給false值一樣的結果。如果你給的是true值,那麼你建立的semaphore就會在公平模式下執行。
參見