1. 程式人生 > >【Java併發】Lock 和 Condition

【Java併發】Lock 和 Condition

Lock 和 Condition

Lock 介面

在Lock接口出現之前,Java程式是靠synchronized關鍵字來實現鎖功能的,它提供了與synchronized關鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖,但是提供了多種synchronized關鍵字所不具備的同步特性。

Lock介面提供的synchronized關鍵字不具備的主要特性:

  • 嘗試非阻塞地獲取鎖
  • 獲取到的鎖的執行緒能夠響應中斷
  • 在指定的截止時間之前獲取鎖

一個生動的例子

public class LockExample {
	public static void main(String[] args) {
		Lock lock = new ReentrantLock();
		lock.lock();
		try {

		} finally {
			lock.unlock();
		}
	}
}

ReentrantLock

重入鎖ReentranLock,就是支援重進入的鎖,表示該鎖能夠支援一個執行緒對資源的重複加鎖。除此之外,該鎖還支援獲取鎖時的公平和非公平性選擇。

不支援重入的鎖,當自己獲取鎖之後,再獲取鎖的時候,該執行緒就會被自己阻塞,synchronized關鍵字也支援重進入。

公平性與否是針對獲取鎖而言的,如果一個鎖是公平的,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。

一個生動的例子

public class FairAndUnfairTest {

	private static ReentrantLock2 fairLock = new ReentrantLock2(true);
	private static ReentrantLock2 unfairLock = new ReentrantLock2(false);

	@Test
	public void unfairTest() {
		testLock(unfairLock)
; } @Test public void fairTest() { testLock(fairLock); } private void testLock(ReentrantLock2 lock) { for (int i = 0; i < 5; i++) { Job job = new Job(lock); job.setName(String.valueOf(i)); job.start(); } } private static class Job extends Thread { private ReentrantLock2 lock; public Job(ReentrantLock2 lock) { this.lock = lock; } @Override public void run() { lock.lock(); try { System.out.println("Lock By[" + Thread.currentThread() + "], Waiting by " + lock.getQueuedThreads()); System.out.println("Lock By[" + Thread.currentThread() + "], Waiting by " + lock.getQueuedThreads()); } finally { lock.unlock(); } } @Override public String toString() { return super.getName(); } } private static class ReentrantLock2 extends ReentrantLock { private static final long serialVersionUID = 1L; public ReentrantLock2(boolean fair) { super(fair); } public Collection<Thread> getQueuedThreads() { List<Thread> arrayList = new ArrayList<>(super.getQueuedThreads()); Collections.reverse(arrayList); return arrayList; } } }

輸出的結果可能是:

公平鎖 非公平鎖
Lock By[0], Waiting by [] Lock By[0], Waiting by []
Lock By[0], Waiting by [1, 2] Lock By[0], Waiting by [1, 2]
Lock By[1], Waiting by [2, 4] Lock By[1], Waiting by [2]
Lock By[1], Waiting by [2, 4, 3] Lock By[1], Waiting by [2]
Lock By[2], Waiting by [4, 3] Lock By[3], Waiting by [2]
Lock By[2], Waiting by [4, 3] Lock By[3], Waiting by [2]
Lock By[4], Waiting by [3] Lock By[4], Waiting by [2]
Lock By[4], Waiting by [3] Lock By[4], Waiting by [2]
Lock By[3], Waiting by [] Lock By[2], Waiting by []
Lock By[3], Waiting by [] Lock By[2], Waiting by []

每條執行緒輸出兩條資訊,從結果可以看出,公平性鎖每次都是從同步佇列中的第一個節點獲取鎖,而非公平鎖不一定。

ReentrantReadWriteLock

排他鎖就是在同一個時刻只允許一個執行緒進行訪問,而讀寫鎖在同一時刻可以允許多個讀執行緒訪問,但是在寫執行緒訪問時,所有的讀執行緒和寫執行緒均被阻塞。

讀寫鎖維護了一個讀鎖和一個寫鎖,支援公平性選擇、重進入、鎖降級(遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖可以降級為讀鎖)。

一個生動的例子

public class ReentrantReadWriteLockExample {
	private static Map<String, Object> map = new HashMap<String, Object>();
	private static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private static Lock r = rwl.readLock();
	private static Lock w = rwl.writeLock();

	public static final Object get(String key) {
		r.lock();
		try {
			return map.get(key);
		} finally {
			r.unlock();
		}
	}

	public static final Object put(String key, Object value) {
		w.lock();
		try {
			return map.put(key, value);
		} finally {
			w.unlock();
		}
	}

	public static final void clear() {
		w.lock();
		try {
			map.clear();
		} finally {
			w.unlock();
		}
	}
}

用一個非執行緒安全的HashMap作為快取的實現,同時使用讀寫鎖的讀鎖和寫鎖來保證HashMap是執行緒安全的。

Condition

任意一個Java物件,都有一組監視器方法,主要包括wait()、wait(long)、notify()以及notifyAll()方法,這些方法與synchronized關鍵字配合,可以實現等待/通知模式。

Condition介面也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式。它支援的特性如下:

  • 多個等待佇列
  • 在等待狀態中不響應中斷
  • 等待到將來的某個時間。

一個生動的例子

public class ConditionExample {
	private Object[] items;

	private int addIndex, removeIndex, count;
	private Lock lock = new ReentrantLock();
	private Condition notEmpty = lock.newCondition();
	private Condition notFull = lock.newCondition();

	public ConditionExample(int size) {
		items = new Object[size];
	}

	public void add(Object t) throws InterruptedException {
		lock.lock();
		try {
			while (count == items.length) {
				notFull.await();
			}

			items[addIndex] = t;
			if (++addIndex == items.length) {
				addIndex = 0;
			}
			count++;
			notEmpty.signal();
		} finally {
			lock.unlock();
		}
	}

	public Object remove() throws InterruptedException {
		lock.lock();
		try {
			while (count == 0) {
				notEmpty.await();
			}
			Object result = items[removeIndex];
			if (++removeIndex == items.length) {
				removeIndex = 0;
			}
			count--;
			notFull.signal();
			return result;
		} finally {
			lock.unlock();
		}
	}
}

參考

  1. Java併發程式設計的藝術[書籍]