1. 程式人生 > >實現自定義Lock類

實現自定義Lock類

Java 9併發程式設計指南 目錄

實現自定義Lock類

鎖是Java併發API提供的基本同步機制之一,每次只有一個執行緒可以執行程式碼塊,因此用來保護程式碼的關鍵部分。鎖機制提供如下兩種操作:

  • lock():當訪問臨界區時呼叫此操作,如果執行緒正在執行此臨界區,其它執行緒將被阻塞直到鎖得到臨界區訪問許可權時才被喚醒。
  • unlock():在臨界區結尾呼叫此方法,允許其它執行緒訪問臨界區。

在Java併發API中,鎖在Lock介面中宣告,且在一些類中實現,例如ReentrantLock類。

本節將通過實現Lock介面的類學習如何實現自定義Lock物件,用來保護臨界區。

準備工作

本範例通過Eclipse開發工具實現。如果使用諸如NetBeans的開發工具,開啟並建立一個新的Java專案。

實現過程

通過如下步驟實現範例:

  1. 建立名為MyAbstractQueuedSynchronizer的類,繼承AbstractQueuedSynchronizer類:

    public class MyAbstractQueuedSynchronizer extends AbstractQueuedSynchronizer{
    
  2. 宣告名為state的私有AtomicInteger屬性:

    	private final AtomicInteger state;
    
  3. 實現類建構函式,初始化屬性:

    	public MyAbstractQueuedSynchronizer() {
    		state=new AtomicInteger(0);
    	}
    
  4. 實現tryAcquire()方法,此方法試圖將狀態變數值從0變成1。如果改變則返回true值,否則返回false:

    	@Override
    	protected boolean tryAcquire(int arg) {
    		return state.compareAndSet(0, 1);
    	}
    
  5. 實現tryRelease()方法,此方法試圖將狀態變數值從1變成0。如果改變則返回true值,否則返回false:

    	@Override
    	protected boolean tryRelease(int arg) {
    		return state.compareAndSet(1, 0);
    	}
    }
    
  6. 建立名為MyLock的類,指定其實現Lock介面:

    public class MyLock implements Lock {
    
  7. 宣告名為sync的私有AbstractQueuedSynchronizer屬性:

    	private final MyAbstractQueuedSynchronizer 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();
    			Thread.currentThread().interrupt();
    			return false;
    		}
    	}
    
  12. 實現tryLock()方法的另一個版本,包含兩個引數:名為time的長整型引數和unit的TimeUnit引數。呼叫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. 宣告名為lock的私有MyLock屬性:

    	private final MyLock lock;
    
  17. 宣告名為name的私有String屬性:

    	private final String name;
    
  18. 實現類建構函式,初始化屬性:

    	public Task(String name, MyLock lock){
    		this.lock=lock;
    		this.name=name;
    	}
    
  19. 實現類的run()方法,獲取鎖,設定執行緒休眠2秒鐘,然後釋放鎖物件:

    	@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. 建立名為lock的MyLock物件:

    		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。

在這些方法中,需要實現用來控制訪問臨界區的機制。本範例中,實現了MyAbstractQueuedSynchonizer類,此類繼承AbstractQueuedSyncrhonizer類,並且使用AtomicInteger變數實現抽象方法來控制訪問臨界區。如果鎖是空閒的,這個變數儲存值為0,,因此執行緒能夠訪問臨界區。如果鎖是阻塞的話,則值為1,執行緒無法訪問臨界區。

  • 還用到AtomicInteger類提供的compareAndSet()方法,此方法試圖更改指定為第一個引數的值,並將值指定為第二個引數。為了實現tryAcquire()方法,試圖將原子變數值從零變成一。同樣地,為了實現tryRelease()方法,試圖將原子變數值從一變成零。

因為AbstractQueuedSynchronizer類的其它實現(例如,通過ReentrantLock使用的實現)作為私有類在內部實現,所以必須實現AtomicInteger類。這是在使用它的類中執行的,所以無權訪問它。

然後實現了MyLock類,此類實現了Lock介面且具有作為屬性的MyQueuedSynchronizer物件。為了實現Lock介面的所有方法,使用了MyQueuedSynchronizer物件的方法。

最後,實現了Task類,此類實現了Runnable介面,且使用MyLock物件獲得臨界區訪問權,此臨界區設定執行緒休眠2秒鐘。main類建立了MyLock物件,然後執行10個共享此鎖的Task物件。main類還使用tryLock()方法試圖獲得鎖訪問權。

當執行本範例時,能過觀察到如何只有一個執行緒能夠訪問臨界區,並且當執行緒結束執行時,下一個執行緒接著訪問臨界區。

也能夠使用自定義的Lock介面輸出介面使用情況的日誌資訊,控制介面鎖定的時間,或者實現高階的同步機制進行控制,例如訪問資源,使其只能在特定時間可用。

擴充套件學習

AbstractQueuedSynchronizer類提供了兩個方法,能夠用來管理鎖的狀態,分別是getState()和setState()方法。這些方法接收和返回包含鎖狀態的整型值,可以使用它們代替AtomicInteger屬性來儲存鎖狀態。

AbstractQueuedLongSynchronizer類是Java併發API提供的另一個實現同步機制的類,與AbstractQueuedSynchronizer類相同,但它使用long屬性儲存執行緒狀態。

更多關注

  • 第二章“基礎執行緒同步”中的“鎖同步程式碼塊”小節