Java模擬ReentrantLock實現自己的顯示鎖BooleanLock
一、前言
Java通過synchronized關鍵字來為我們提供執行緒安全的保證,大多數情況下使用synchronized是沒有問題的,然而synchronized有自身的缺陷。例如:當其它執行緒持有鎖時,當前請求獲取鎖的執行緒必須等待。等待的時長是無法控制的,而且等待過程中無法響應中斷。
正是為了解決synchronized這些的缺陷,Java提供了一個顯示鎖ReentrantLock來實現執行緒安全,在滿足重入性,獨佔性的條件下,為我們的加鎖,解鎖和鎖獲取提供了靈活的操作。
ReentrantLock的常用方法如下:
lockInterruptibly():提供響應中斷的鎖獲取操作。
tryLock(long timeout, TimeUtil unit):指定時間內響應中斷和指定時間內的鎖獲取操作。
tryLock():以非阻塞的方法嘗試獲取鎖,無論是否獲取鎖都立刻返回,當成功獲取鎖返回true,否則返回false。
new ReentrantLock(boolean):構造方法可指定鎖獲取的公平性,即先申請獲得鎖的執行緒將先獲得鎖。synchronized是非公平的。
lock():以阻塞的形式獲取鎖,和使用synchronized一樣的效果。
unlock():解鎖。
isHeldByCurrentThread():持有鎖的執行緒是否為當前執行緒。
本文將以ReentrantLock的這些特性為目標,通過synchronized+wait+notify來模擬一個自己的顯示鎖。程式碼靈感來源於 汪文君 老師的一書《高併發程式設計詳解:多執行緒與架構設計》。
二、程式碼部分
1.定義自己的顯示鎖,它是一個介面,滿足的功能參照備註:
import java.util.List; import java.util.concurrent.TimeoutException; public interface MyLock { /** * 實現可中斷的鎖獲取操作 * * @throws InterruptedException */ void lock() throws InterruptedException; /** * 實現限時的鎖獲取操作 * * @param timeMillis * @throws InterruptedException * @throws TimeoutException */ void lock(long timeMillis) throws InterruptedException, TimeoutException; /** * 解鎖 */ void unLock(); /** * 返回等待獲取鎖的執行緒集合 * * @return */ List<Thread> getBlockedThreads(); /** * 嘗試獲取鎖,獲取成功立刻返回true,否則返回false * * @return */ boolean tryLock(); /** * 判斷當前執行緒是否持有鎖 * * @return */ boolean isHeldByCurrentThread(); }
MyLock介面模擬了ReentrantLock的部分方法:MyLock的lock()方法模擬ReentrantLock的lockInterruptibly(),MyLock的lock(long timeMillis)方法模擬ReentrantLock的tryLock(long timeout, TimeUtil unit),MyLock的其餘方法模擬ReentrantLock的同名方法。
2.定義BooleanLock,作為MyLock的實現類,程式碼如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
public class BooleanLock implements MyLock {
private boolean locked;
private Thread currentThread; //當前持有鎖的執行緒
private final List<Thread> blockedThreads = new ArrayList<>(); //等待獲取鎖的執行緒
@Override
public void lock() throws InterruptedException {
synchronized (this) {
while (locked) {
if (!blockedThreads.contains(Thread.currentThread())) {
blockedThreads.add(Thread.currentThread());
}
try {
this.wait();
} catch (InterruptedException e) {
blockedThreads.remove(Thread.currentThread());
throw e;
}
}
locked = true;
currentThread = Thread.currentThread();
blockedThreads.remove(currentThread);
}
}
@Override
public void lock(long timeMillis) throws InterruptedException, TimeoutException {
synchronized (this) {
if (timeMillis <= 0) {
this.lock();
} else {
long remainMills = timeMillis;
long endTime = System.currentTimeMillis() + remainMills; //截至時刻
while (locked) {
if (remainMills <= 0) { //剩餘等待時間
throw new TimeoutException("cannot get lock during " + remainMills);
}
if (!blockedThreads.contains(Thread.currentThread())) {
blockedThreads.add(Thread.currentThread());
}
this.wait(remainMills); //等待一定時間
remainMills = endTime - System.currentTimeMillis(); //重新計算等待時間:當前時間點與while執行之前的時間點之差。
}
locked = true;
currentThread = Thread.currentThread();
blockedThreads.remove(currentThread);
}
}
}
@Override
public void unLock() {
synchronized (this) {
if (currentThread == Thread.currentThread()) {
locked = false;
this.notifyAll();
}
}
}
@Override
public List<Thread> getBlockedThreads() {
return Collections.unmodifiableList(blockedThreads);
}
@Override
public boolean tryLock() {
synchronized (this) {
if (!locked) {
locked = true;
currentThread = Thread.currentThread();
blockedThreads.remove(currentThread);
return true;
}
return false;
}
}
@Override
public boolean isHeldByCurrentThread() {
synchronized (this) {
if (Thread.currentThread() == currentThread) {
return true;
}
return false;
}
}
}
三.測試程式碼
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class TestMyLock {
private Random random = new Random();
private MyLock myLock = new BooleanLock();
public void syncMethod() {
try {
myLock.lock();
int rand = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
TimeUnit.SECONDS.sleep(rand);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (myLock.isHeldByCurrentThread()) {
myLock.unLock();
System.out.println(Thread.currentThread().getName() + " release lock");
}
}
}
public void syncTryMethod() {
try {
if (myLock.tryLock()) {
int rand = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
TimeUnit.SECONDS.sleep(rand);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (myLock.isHeldByCurrentThread()) {
myLock.unLock();
System.out.println(Thread.currentThread().getName() + " release lock");
}
}
}
public void syncMethodOut() {
try {
myLock.lock(1000);
int rand = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
TimeUnit.SECONDS.sleep(rand);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (myLock.isHeldByCurrentThread()) {
myLock.unLock();
System.out.println(Thread.currentThread().getName() + " release lock");
}
}
}
static void testSync() {
final TestMyLock testMyLock = new TestMyLock();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
testMyLock.syncMethod();
}, i + "");
thread.start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(testMyLock.myLock.getBlockedThreads());
}
static void testTrySync() {
final TestMyLock testMyLock = new TestMyLock();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
testMyLock.syncTryMethod();
}, i + "");
thread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(testMyLock.myLock.getBlockedThreads());
}
static void testInterrupt() {
final TestMyLock testMyLock = new TestMyLock();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
testMyLock.syncMethod();
}, i + "");
thread.start();
if (i % 2 == 0) {
thread.interrupt();
}
}
}
static void testTimeOut() throws InterruptedException {
TestMyLock blt = new TestMyLock();
new Thread(blt::syncMethod, "Tl").start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(blt::syncMethodOut, "T2");
t2.start();
}
public static void main(String[] args) throws InterruptedException {
testSync();
// testTrySync();
// testInterrupt();
// testTimeOut();
}
}
1.執行testSync(),看到控制檯有條不紊的打印出加鎖和解鎖的操作,說明MyLock實現了執行緒同步的功能。
0 hold lock, and will sleep 5 seconds
[Thread[1,5,main], Thread[2,5,main], Thread[5,5,main], Thread[4,5,main], Thread[3,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
0 release lock
9 hold lock, and will sleep 3 seconds
9 release lock
1 hold lock, and will sleep 3 seconds
1 release lock
8 hold lock, and will sleep 8 seconds
8 release lock
2 hold lock, and will sleep 6 seconds
2 release lock
7 hold lock, and will sleep 6 seconds
7 release lock
5 hold lock, and will sleep 8 seconds
5 release lock
6 hold lock, and will sleep 5 seconds
6 release lock
4 hold lock, and will sleep 3 seconds
4 release lock
3 hold lock, and will sleep 7 seconds
3 release lock
2.執行testTrySync(),控制檯列印如下,可以看出使用tryLock時,若執行緒0持有鎖,其它執行緒無法獲取鎖,故等待獲取鎖的集合為空集合。
0 hold lock, and will sleep 4 seconds
[]
0 release lock
3.執行testInterrupt(),控制檯列印如下
java.lang.InterruptedException
0 hold lock, and will sleep 8 seconds
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
0 release lock
at BooleanLock.lock(BooleanLock.java:20)
9 hold lock, and will sleep 4 seconds
at TestMyLock.syncMethod(TestMyLock.java:12)
at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at TestMyLock.syncMethod(TestMyLock.java:15)
at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at BooleanLock.lock(BooleanLock.java:20)
at TestMyLock.syncMethod(TestMyLock.java:12)
at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at BooleanLock.lock(BooleanLock.java:20)
at TestMyLock.syncMethod(TestMyLock.java:12)
at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at BooleanLock.lock(BooleanLock.java:20)
at TestMyLock.syncMethod(TestMyLock.java:12)
at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
at java.lang.Thread.run(Thread.java:745)
9 release lock
1 hold lock, and will sleep 1 seconds
1 release lock
7 hold lock, and will sleep 0 seconds
7 release lock
3 hold lock, and will sleep 7 seconds
3 release lock
5 hold lock, and will sleep 6 seconds
5 release lock
這裡的列印有點亂,要注意e.printStackTrace方法本質上是呼叫System.err物件的println方法,而System.out.println方法屬於System.out物件,故它們是屬於不同的物件,也就是執行緒非同步的!可以看出0,2,4,6執行緒都響應了中斷!
4.執行testTimeOut(),控制檯列印如,可以看出當執行緒T1已經獲取鎖,並且睡眠3s,執行緒T2嘗試在1s內獲取鎖,結果失敗。
Tl hold lock, and will sleep 3 seconds
java.util.concurrent.TimeoutException: cannot get lock during 0
at BooleanLock.lock(BooleanLock.java:42)
at TestMyLock.syncMethodOut(TestMyLock.java:47)
at java.lang.Thread.run(Thread.java:745)
Tl release lock
四、小結
本文演示的案例僅僅是為了更好的理解顯示鎖特有的性質,ReentrantLock自身的實現運用了大量的AQS和CAS操作,其細節和原理遠比本文複雜的多。實際專案開發我們直接使用ReentrantLock即可,不必大費周章定義自己的顯示鎖。