一、前言
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即可,不必大费周章定义自己的显示锁。