【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();
}
}
}
參考
- Java併發程式設計的藝術[書籍]