Java Concurrent Programming (4)

http://www.4ucode.com/Study/Topic/2011006


Java Concurrent Programming (4)

4 鎖
  鎖是遞歸的,是基於每線程的。鎖提供了兩種主要特性:互斥(mutual exclusion)和可見性(visibility)。互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享數據。可見性要更加複雜一些,它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的。Java語言中,使用synchronized關鍵字可以實現鎖機制,但是它有很多限制:
  1)無法中斷一個正在等候獲得鎖的線程。
  2)沒有辦法改變鎖的語意,即重入性,獲取鎖的公平性等。
  3)方法和塊內的同步,使得只能夠夠對嚴格的塊結構使用鎖。例如:不能在一個方法中獲得鎖,而在另外一個方法中釋放鎖。
在這種情況下,引入了java.util.concurrent.locks具有編寫的高質量、高效率、語義上準確的線程控制結構工具包。

4.1 ReentrantLock類
  ReentrantLock具有與內部鎖相同的互斥、重入性和內存可見性的保證,它必須被顯式地釋放。ReentrantLock是可中斷的、可定時的,非塊結構鎖。在Java5中,ReentrantLock的性能要遠遠高於內部鎖。在Java6中,由於管理內部鎖的算法採用了類似於ReentrantLock使用的算法,因此內部鎖和ReentrantLock之間的性能差別不大。
  reentrant 鎖意味着什麼呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。
  ReentrantLock的構造函數有兩個:
public ReentrantLock();
public ReentrantLock(boolean fair);

  fair表示是否是公平性,true表示是公平性獲取鎖。默認的構造是非公平性獲取鎖。在公平鎖中,如果鎖已被其它線程佔有,那麼請求線程會加入到等待隊列中,並按順序獲得鎖;在非公平鎖中,當請求鎖的時候,如果鎖的狀態是可用,那麼請求線程可以直接獲得鎖,而不管等待隊列中是否有線程已經在等待該鎖。公平鎖的代價是更多的掛起和重新開始線程的性能開銷。在多數情況下,非公平鎖的性能高於公平鎖。一下是一個例子:
public class ReentrantLockDemo{
	
	//
	private int count;
	
	private final int CAPACITY = 1;
	
	private final Lock lock = new ReentrantLock();
	
	private final Condition isEmpty = lock.newCondition();
	
	private final Condition isFull = lock.newCondition();
	
	private final Object[] cache = new Object[CAPACITY];
	
	public ReentrantLockDemo(int count){
		this.count = count;
	}
	
	public Object get() throws InterruptedException{
		lock.lock();
		try {
			while(isEmpty()){
				isEmpty.await();
			}
			count--;
			isFull.signal();
			return cache[count];
		} finally{
			lock.unlock();
		}
	}
	
	public void put() throws InterruptedException{
		lock.lock();
		try {
			while(isFull()){
				isFull.await();
			}
			count++;
			cache[0] = Thread.currentThread().getName() + "put a message";
			System.out.println(cache[0]);
			isEmpty.signal();
		} finally{
			lock.unlock();
		}
	}
	
	public boolean isFull(){
		return count == cache.length;
	}
	
	public boolean isEmpty(){
		return count == 0;
	}
}


4.2 ReentrantReadWriteLock類
  ReentrantReadWriteLock支持與ReentrantLock類似語義的ReadWriteLock實現,將控制的更細粒化,將鎖分爲讀鎖和寫鎖。其也支持公平性鎖競爭的策略。當一個線程申請讀鎖時,只要寫入鎖沒有被其他線程持有,那麼會立刻擁有讀鎖。當一個線程申請寫入鎖時,其他線程不能持有讀鎖和寫入鎖,否則會一直等待。如果當前線程已經持有寫入鎖,然後再次申請寫入鎖,會成功,但是需要釋放兩次鎖。以下是一個小例子:
public class ReentrantReadWriteLockDemo {
	
	//
	private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	
	private final Lock read = rwl.readLock();
	
	private final Lock write = rwl.writeLock();
	
	private final Map<String,Object> map = new HashMap<String, Object>();
	
	public Object get(String key) {
		read.lock();
		try {
			return map.get(key);
		} finally{
			read.unlock();
		}
	}
	
	public void set(String key, Object value){
		write.lock();
		try {
			map.put(key, value);
		} finally{
			write.unlock();
		}
	}
	
	public void clear(){
		write.lock();
		try {
			map.clear();
		} finally{
			write.unlock();
		}
	}
}


4.3 Semaphore
  信號量,其維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數。
  信號量通常用於限制可以訪問某些資源的線程數目。下面這個小程序會幫助您理解Semaphore:
public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final Semaphore semaphore = new Semaphore(5);
		for (int index = 0; index < 20; index++) {
			final int NO = index;
			Runnable run = new Runnable() {
				public void run() {
					try {
						semaphore.acquire();
						System.out.println("acquire thread" + NO + "***********");
						Thread.sleep(3000);
						semaphore.release();
						System.out.println("release thread" + NO + "###########");
					} catch (Exception e) {
					}
				}
			};
			service.execute(run);
		}
		service.shutdown();
	}


4.4 Mutex
  非重入互斥獨佔鎖(non-reentrant mutual exclusion lock),它不提供公平性鎖競爭策略已經順序化資源的保證。如果鎖已經被執行acquire的線程持有,當再次acquire後,會形成阻塞。Mutex可以看做信號量爲1的Semaphore。其acquire和release方法,可以不用成對在同一個方法中出現。其定義如下:
public class Mutex implemets Sync {
    public void acquire() throws InterruptedException;
    public void release();
    public boolean attempt(long msec) throws InterruptedException;
}

如果當前線程在試圖acquire和attempt獲取鎖的過程中被中斷,會拋出InterruptedException異常。

4.5 Latch
  閉鎖,一個release操作將使得所有之前或者之後的acquire操作都恢復執行。
util.concurrent下的CountDownLatch是其的擴展,其主要方法爲countDown方法和await方法,前者表示計數減一,後者表示等待計數爲0,如果計數不爲0,就形成阻塞,一直阻塞。以下是一個例子,模擬百米才跑比賽,所有人到達終點後,比賽結束:
public class CountDownLatchDemo {
	//
	private static final int PLAY_AMOUNT = 10;
	
	public static void main(String[] args) {
		
		CountDownLatch begin = new CountDownLatch(1);
		
		CountDownLatch end = new CountDownLatch(PLAY_AMOUNT);
		
		Player[] players = new Player[PLAY_AMOUNT];
		
		for(int i = 0; i < players.length; i ++){
			players[i] = new Player(i+1, begin, end);
		}
		
		ExecutorService executor = Executors.newFixedThreadPool(PLAY_AMOUNT);
		
		for(Player player : players){
			executor.execute(player);
		}
		
		System.out.println("game begin ");
		begin.countDown();
		try {
			end.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("game over ");
		executor.shutdown();
	}
	
}

class Player implements Runnable{
	//
	private final int id;
	
	private final CountDownLatch begin;
	
	private final CountDownLatch end;
	
	public Player(int id, CountDownLatch begin, CountDownLatch end){
		this.id = id;
		this.begin = begin;
		this.end = end;
	}
	
	public void run(){
		try {
			begin.await();
			Thread.sleep((long)(Math.random() * 100));
			System.out.println("Player " + id + " has arrived");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			end.countDown();
		}
	}
}

4.6 Barrier
  Barrier能夠阻塞一組線程,其與閉鎖的區別在於:閉鎖等待的是事件,barrier等待的是線程。CyclicBarrier表示的cyclic就是表示,線程在釋放後可以重用。有兩個構造函數:
public CyclicBarrier(int parties, Runnable barrierAction);
public CyclicBarrier(int parties);

  parties表示可阻擋線程數量,barrierAction表示線程數量到達parties後執行的動作,null表示沒有執行動作。第二個版本的構造函數默認爲null的執行動作。以下這個小例子,會幫助您理解CyclicBarrier,模擬5人去登山,當5人都到齊了,便出發:
public class CyclicBarrierDemo {
	
	public static void main(String[] args) {
		
		final ExecutorService executor = Executors.newCachedThreadPool();
		
		final CyclicBarrier barrier = new CyclicBarrier(5, new Runnable(){
			
			public void run(){
				System.out.println("all people arrived , let's go");
			}
		});
		
		for(int i = 0; i < 5; i ++){
			executor.execute(new Runnable(){
				
				public void run(){
					try {
						Thread.sleep((long)(Math.random() * 100));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getId() + " has arrived");
					try {
						barrier.await();  // wait other people to arrive
					} catch (InterruptedException e) {
						e.printStackTrace();
					} catch (BrokenBarrierException e) {
						e.printStackTrace();
					}
				}
			});
		}
		executor.shutdown();
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章