Table of Contents
ToggleJava ReentrantLock Class
實現執行緒同步的傳統方法,是使用 synchronized 關鍵字,雖然提供了一定的基本同步,但 synchronized 關鍵字的使用非常嚴格,例如,一個執行緒只能取得一次鎖,同步塊不提供任何等待佇列的機制,並且在一個執行緒退出後,任何執行緒都可以取得鎖,這可能會導致其他執行緒在很長一段時間內資源匱乏, ReentrantLock Java 提供了可重入的互斥鎖,又被稱為獨占鎖,實現了 Lock 介面,擁有與 synchronized 相同的同步加鎖功能,ReentrantLock Java Examples 本篇增加了範例,並透過單元測試來驗證產出結果。
檔案目錄
./
+- src
+- test
| +- org
| +- ruoxue
| +- java_147
| +- synchronization
| +- reentrantlock
| +- ReentrantLockClassTest.java
單元測試
Java Lock 提供加鎖、解鎖等操作。
nonfairLock
Java Lock 建立 5 個執行緒,使用非公平鎖,對計數器加 1 ,然後結束任務。
protected class NonfairLockWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public NonfairLockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println("T[" + Thread.currentThread().getId() + "] lock acquired");
count++;
System.out.println("T[" + Thread.currentThread().getId() + "] count: " + count);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println("T[" + Thread.currentThread().getId() + "] lock released");
}
}
public int getCount() {
return count;
}
}
@Test
public void nonfairLock() {
int expected = 5;
int taskSize = 5;
NonfairLockWorker worker = new NonfairLockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
T[11] lock acquired
T[11] count: 1
T[14] lock acquired
T[14] count: 2
T[14] lock released
T[11] lock released
T[15] lock acquired
T[15] count: 3
T[15] lock released
T[12] lock acquired
T[12] count: 4
T[12] lock released
T[13] lock acquired
T[13] count: 5
T[13] lock released
5
fairLock
Java Lock 建立 5 個執行緒,使用公平鎖,對計數器加 1 ,然後結束任務。
protected class FairLockWorker implements Runnable {
private final Lock lock = new ReentrantLock(true);
private int count;
public FairLockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println("T[" + Thread.currentThread().getId() + "] lock acquired");
count++;
System.out.println("T[" + Thread.currentThread().getId() + "] count: " + count);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println("T[" + Thread.currentThread().getId() + "] lock released");
}
}
public int getCount() {
return count;
}
}
@Test
public void fairLock() {
int expected = 5;
int taskSize = 5;
FairLockWorker worker = new FairLockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
T[11] lock acquired
T[11] count: 1
T[11] lock released
T[12] lock acquired
T[12] count: 2
T[12] lock released
T[13] lock acquired
T[13] count: 3
T[13] lock released
T[14] lock acquired
T[14] count: 4
T[14] lock released
T[15] lock acquired
T[15] count: 5
T[15] lock released
5
reentrant
Java Lock 建立 1 個執行緒,調用 1 個有加鎖的方法,此方法內又調用另一個有加鎖的方法,等待 1 秒後結束任務。
protected class ReentrantWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public ReentrantWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] run() lock acquired", Thread.currentThread().getId()));
doCount();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] run() lock released", Thread.currentThread().getId()));
}
}
public void doCount() {
lock.lock();
try {
System.out.println(String.format("T[%d] doCount() lock acquired", Thread.currentThread().getId()));
TimeUnit.SECONDS.sleep(1);
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] doCount() lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void reentrant() {
int expected = 1;
int taskSize = 1;
ReentrantWorker worker = new ReentrantWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
T[11] run() lock acquired
T[11] doCount() lock acquired
T[11] count: 1
T[11] doCount() lock released
T[11] run() lock released
1
brokenUnlock
Java Lock 建立 1 個執行緒,加鎖對計數器加 1 ,等待 1 秒後結束任務,然後解鎖,再第 2 次解鎖,Lock Java 會拋出例外。
protected class BrokenUnlockWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public BrokenUnlockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
TimeUnit.SECONDS.sleep(1);
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void brokenUnlock() {
int expected = 1;
int taskSize = 1;
BrokenUnlockWorker worker = new BrokenUnlockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
T[11] lock acquired
T[11] count: 1
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at org.ruoxue.java_147.synchronization.ReentrantLockClassTest$BrokenUnlockWorker.run(ReentrantLockClassTest.java:194)
at java.lang.Thread.run(Thread.java:750)
1
ReentrantLockClassTest.java
Lock Java 新增單元測試,驗證 ReentrantLock Java 是否符合預期。
package org.ruoxue.java_147.synchronization.reentrantlock;
import static org.junit.Assert.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
public class ReentrantLockClassTest {
protected class NonfairLockWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public NonfairLockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void nonfairLock() {
int expected = 5;
int taskSize = 5;
NonfairLockWorker worker = new NonfairLockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
protected class FairLockWorker implements Runnable {
private final Lock lock = new ReentrantLock(true);
private int count;
public FairLockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void fairLock() {
int expected = 5;
int taskSize = 5;
FairLockWorker worker = new FairLockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
protected class ReentrantWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public ReentrantWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] run() lock acquired", Thread.currentThread().getId()));
doCount();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] run() lock released", Thread.currentThread().getId()));
}
}
public void doCount() {
lock.lock();
try {
System.out.println(String.format("T[%d] doCount() lock acquired", Thread.currentThread().getId()));
TimeUnit.SECONDS.sleep(1);
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
System.out.println(String.format("T[%d] doCount() lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void reentrant() {
int expected = 1;
int taskSize = 1;
ReentrantWorker worker = new ReentrantWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
protected class BrokenUnlockWorker implements Runnable {
private final Lock lock = new ReentrantLock();
private int count;
public BrokenUnlockWorker() {
}
@Override
public void run() {
lock.lock();
try {
System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
TimeUnit.SECONDS.sleep(1);
count++;
System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
}
}
public int getCount() {
return count;
}
}
@Test
public void brokenUnlock() {
int expected = 1;
int taskSize = 1;
BrokenUnlockWorker worker = new BrokenUnlockWorker();
List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
threads.forEach(e -> e.start());
threads.forEach(e -> {
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
});
int count = worker.getCount();
System.out.println(count);
assertEquals(expected, count);
}
}
心得分享
ReentrantLock Java Examples 可以分為公平鎖和非公平鎖,區別在於取得鎖的機制上是否公平, Java Lock 是為了保護競爭資源,防止多個執行緒同時操作而出錯,在同一個時間點只能被一個執行緒取得鎖,其它執行緒若同時想要取得鎖,就必須等待, Lock Java 是通過一個 FIFO 的等待佇列來管理取得該鎖的所有執行緒,在公平鎖的機制下,執行緒依次排隊取得鎖,而非公平鎖是在可取得的狀態時,不管自己是不是在佇列的開頭都可以取得鎖。