定製併發類(九)實現一個自定義的Lock類
宣告:本文是《 Java 7 Concurrency Cookbook 》的第七章,作者: Javier Fernández González 譯者:許巧輝
實現一個自定義的Lock類
鎖是Java併發API提供的基本同步機制之一。它允許程式設計師保護程式碼的臨界區,所以,在某個時刻只有一個執行緒能執行這個程式碼塊。它提供以下兩種操作:
- lock():當你想要訪問一個臨界區時,呼叫這個方法。如果有其他執行緒正在執行這個臨界區,其他執行緒將阻塞,直到它們被這個鎖喚醒,從而獲取這個臨界區的訪問。
- unlock():你在臨界區的尾部呼叫這個方法,允許其他執行緒訪問這個臨界區。
在Java併發API中,鎖是在Lock介面及其一些實現類中宣告的,比如ReentrantLock類。
在這個指南中,你將學習如何實現你自己的Lock物件,它將實現一個實現了Lock介面並可用來保護臨界區的類。
準備工作…
這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。
如何做…
按以下步驟來實現的這個例子:
1.建立一個繼承AbstractQueuedSynchronizer類的MyQueuedSynchronizer類。
public class MyAbstractQueuedSynchronizer extends AbstractQueuedSynchronizer {
2.宣告一個私有的、AtomicInteger型別的屬性state。
private AtomicInteger state;
3.實現這個類的構造器,並初始化它的屬性。
public MyAbstractQueuedSynchronizer() { state=new AtomicInteger(0); }
4.實現tryAcquire()方法。這個方法試圖將變數state的值從0變成1。如果成功,它將返回true,否則,返回false。
@Override protected boolean tryAcquire(int arg) { return state.compareAndSet(0, 1); }
5.實現tryRelease()方法。這個方法試圖將變數sate的值從1變成0.如果成功,它將返回true,否則,返回false。
@Override protected boolean tryRelease(int arg) { return state.compareAndSet(1, 0); }
6.建立一個MyLock類,並指定它實現Lock介面。
public class MyLock implements Lock{
7.宣告一個私有的、AbstractQueuedSynchronizer型別的屬性sync。
private AbstractQueuedSynchronizer sync;
8.實現這個類的構造器,並使用MyAbstractQueueSynchronizer物件來初始化它的sync屬性。
public MyLock() { sync=new MyAbstractQueuedSynchronizer(); }
9.實現lock()方法。呼叫sync物件的acquire()方法。
@Override public void lock() { sync.acquire(1); }
10.實現lockInterruptibly()方法。呼叫sync物件的acquireInterruptibly()方法。
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
11.實現tryLock()方法。呼叫sync物件的tryAcquireNanos()方法。
@Override public boolean tryLock() { try { return sync.tryAcquireNanos(1, 1000); } catch (InterruptedException e) { e.printStackTrace(); return false; } }
12.實現其他版本的帶有兩個引數的tryLock()方法。一個long型別引數,名為time,一個TimeUnit型別引數,名為unit。呼叫sync物件的tryAcquireNanos()方法。
@Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, TimeUnit.NANOSECONDS. convert(time, unit)); }
13.實現unlock()方法。呼叫sync物件的release()方法。
@Override public void unlock() { sync.release(1); }
14.實現newCondition()方法。建立一個新的sync物件的內部類ConditionObject。
@Override public Condition newCondition() { return sync.new ConditionObject(); }
15.建立一個Task類,並指定它實現Runnable介面。
public class Task implements Runnable {
16.宣告一個私有的、MyLock型別的屬性lock。
private MyLock lock;
17.宣告一個私有的、String型別的屬性name。
private String name;
18.實現這個類的構造器,並初始化它的屬性。
public Task(String name, MyLock lock){ this.lock=lock; this.name=name; }
19.實現這個類的run()方法。獲取鎖,令執行緒睡眠2秒,然後,釋放這個lock物件。
@Override public void run() { lock.lock(); System.out.printf("Task: %s: Take the lock\n",name); try { TimeUnit.SECONDS.sleep(2); System.out.printf("Task: %s: Free the lock\n",name); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
20.實現這個例子的主類,通過建立Main類,並實現main()方法。
public class Main { public static void main(String[] args) {
21.建立一個MyLock物件,名為lock。
MyLock lock=new MyLock();
22.建立和執行10個Task任務。
for (int i=0; i<10; i++){ Task task=new Task("Task-"+i,lock); Thread thread=new Thread(task); thread.start(); }
23.使用tryLock()方法嘗試獲取鎖。等待1秒,如果你沒有獲取鎖,寫入一條資訊並重新嘗試。
boolean value; do { try { value=lock.tryLock(1,TimeUnit.SECONDS); if (!value) { System.out.printf("Main: Trying to get the Lock\n"); } } catch (InterruptedException e) { e.printStackTrace(); value=false; } } while (!value);
24.寫入一條資訊表明你已獲取鎖,然後釋放它。
System.out.printf("Main: Got the lock\n"); lock.unlock();
25.寫入一條資訊表明程式的結束。
System.out.printf("Main: End of the program\n");
它是如何工作的…
Java併發API提供一個類,可以用來實現擁有鎖和訊號量特徵的同步機制。它就是AbstractQueuedSynchronizer,正如其名,它是一個抽象類。它提供控制臨界區的訪問和管理正在阻塞等待訪問臨界區的執行緒佇列的操作。這些操作是基於以下兩個抽象方法:
- tryAcquire():嘗試訪問臨界區時,呼叫這個方法。如果執行緒呼叫這個方法可以訪問臨界區,那麼這個方法返回true,否則,返回false。
- tryRelease():嘗試翻譯臨界區的訪問,呼叫這個方法。如果執行緒呼叫這個方法可以釋放臨界區的訪問,那麼這個方法返回true,否則,返回false.
在這些方法中,你已實現可用來控制臨界區訪問的機制。在你的例子中,你已實現繼承AbstractQueuedSyncrhonizer類的MyQueuedSynchonizer類,並使用AtomicInteger變數實現抽象方法來控制臨界區的訪問。如果鎖是自由的,這個變數的值為0,表明執行緒可以訪問這個臨界區。如果鎖是阻塞的,這個變數的值為1,表明執行緒不能訪問這個臨界區。
你已使用AtomicInteger類提供的compareAndSet()方法,嘗試將你指定的值作為第一個引數改變成你指定的值作為第二個引數。實現tryAcquire()方法,你嘗試將原子變數的值從0變成1。同樣地,你實現tryRelease()方法,嘗試將原子變數的值從1變成0。
你必須實現這個類,因為AbstractQueuedSynchronizer類的其他實現(比如,所使用的ReentrantLock類)是作為私有的內部類使用來實現的,所以你不能訪問它。
然後,你已實現MyLock類。這個類實現Lock介面,有一個MyQueuedSynchronizer物件屬性。你已使用MyQueuedSynchronizer物件的方法,來實現Lock介面的所有方法。
最後,你實現了Task類,它實現了Runnable介面,並使用一個MyLock物件來控制臨界區的訪問。這個臨界區令執行緒睡眠2秒。主類建立一個MyLock物件,並執行10個Task物件來共享這把鎖。主類也使用tryLock()方法來嘗試獲取鎖的訪問。
當你執行這個例子,你可以看到只有一個執行緒可以訪問這個臨界區,並且當這個執行緒結束,其他執行緒可以繼續訪問這個臨界區。
你可以使用你自己的鎖來寫入關於它的使用的日誌資訊,控制鎖定時間,或實現先進的同步機制來控制。比如,只能在特定的時間內,才能對資源訪問。
不止這些…
AbstractQueuedSynchronizer類提供兩個方法可以用來控制鎖的狀態,它們就是getState()和setState()方法。這兩個方法,接收和返回一個整數值作為鎖的狀態。你可以使用這兩個方法而不是AtomicInteger屬性來儲存鎖的狀態。
Java併發API提供其他類來實現同步機制。它就是AbstractQueuedLongSynchronizer類,它與AbstractQueuedSynchronizer一樣,除了使用一個long型別屬性來儲存執行緒的狀態。
參見