一、前言
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即可,不必大費周章定義自己的顯示鎖。