如何实现一个自己的显式锁Lock
如何实现一个自己的显式锁Lock
首先我们通过代码引出一个问题:如果方法上了同步锁并且被一个线程占有了,那么其他线程想执行这个方法就要一直等待下去,如果这个方法死循环或者无限执行下去,等待的线程就会无限等待下去,这里来演示一下这种场景:
/**
* @program: ThreadDemo
* @description: synchronized机制导致的一个问题:synchronized不能被打断,导致其他线程抢不到锁。
* @author: hs96.cn@Gmail.com
* @create: 2020-09-14
*/
public class SynchronizedProblem {
public static void main(String[] args) {
new Thread(SynchronizedProblem::run, "T1").start();
new Thread(SynchronizedProblem::run, "T2").start();
}
private synchronized static void run() {
System.out.println(Thread.currentThread().getName());
while (true) {
//一直执行下去。。。
}
}
}
运行效果如下:
使用interrupt()打断一下T2线程:
public class SynchronizedProblem {
public static void main(String[] args) throws InterruptedException {
new Thread(SynchronizedProblem::run, "T1").start();
Thread.sleep(1000);
Thread t2 = new Thread(SynchronizedProblem::run, "T2");
t2.start();
Thread.sleep(2000);
t2.interrupt();
System.out.println(t2.isInterrupted());// true,但是并没有打断t2
}
private synchronized static void run() {
System.out.println(Thread.currentThread().getName());
while (true) {
//一直执行下去
}
}
}
运行效果如下:
因为同步方法里有while循环,接收不到中断异常的,所以基于这个场景我们来定义一个带有超时功能的锁,思路还是和上一个案例差不多,一个while一直检测lock,再结合wait(),notifyAll(),代码如下:
我们先定义一个Lock interface:
package com.thread.thread20;
import java.util.Collection;
public interface Lock {
class TimeOutException extends Exception {
public TimeOutException(String message) {
super(message);
}
}
/**
* 加锁
*
* @throws InterruptedException 打断异常
*/
void lock() throws InterruptedException;
/**
* 加锁
*
* @param mills 加锁时间
* @throws InterruptedException 打断异常
* @throws TimeOutException 超时异常
*/
void lock(long mills) throws InterruptedException, TimeOutException;
/**
* 解锁
*/
void unlock();
/**
* 获取争抢锁时被阻塞的线程
*
* @return 被阻塞线程的集合
*/
Collection<Thread> getBlockedThread();
/**
* 获取争抢锁时被阻塞的线程的数量
*
* @return 被阻塞的线程的数量
*/
int getBlockedSize();
}
定义一个类来实现接口:
package com.thread.thread20;
import java.util.Collection;
/**
* @program: ThreadDemo
* @description: 自定义显式锁 Boolean Lock
* @author: hs96.cn@Gmail.com
* @create: 2020-09-14
*/
public class BooleanLock implements Lock {
@Override
public void lock() throws InterruptedException {
}
@Override
public void lock(long mills) throws InterruptedException, TimeOutException {
}
@Override
public void unlock() {
}
@Override
public Collection<Thread> getBlockedThread() {
return null;
}
@Override
public int getBlockedSize() {
return 0;
}
}
我们来定义两个成员变量:
initValue
为true
代表正在被占用,false
反之。
blockedThreadCollection
来存放阻塞的线程。
private boolean initValue;
private Collection<Thread> blockedThreadCollection = new ArrayList<>();
public BooleanLock() {
this.initValue = true;
}
完善lock方法:
@Override
public synchronized void lock() throws InterruptedException {
// 锁已经被其他线程使用
while (initValue) {
blockedThreadCollection.add(Thread.currentThread());
this.wait();
}
// 锁未被使用,抢到锁立即设置initValue的值
this.initValue = true;
blockedThreadCollection.remove(Thread.currentThread());
}
这里和上一篇的数据采集很相似。
完善unlock方法:
@Override
public void unlock() {
this.initValue = false;
Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println);
this.notifyAll();
}
查询方法完善如下:
@Override
public Collection<Thread> getBlockedThread() {
return Collections.unmodifiableCollection(blockedThreadCollection);
}
@Override
public int getBlockedSize() {
return blockedThreadCollection.size();
}
return
的Collections
一定使用unmodifiableCollection
因为直接返回的是一个实例,调用者可以随意更改。所以我们把他置为·unmodifiableCollection
。
测试一下代码:
package com.thread.thread20;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @program: ThreadDemo
* @description: 用来调用我们自己写的显示Lock
* @author: hs96.cn@Gmail.com
* @create: 2020-09-14
*/
public class LockTest {
public static void main(String[] args){
final BooleanLock booleanLock = new BooleanLock();
Stream.of("T1", "T2", "T3", "T4").forEach(name -> {
new Thread(() -> {
try {
booleanLock.lock();
Optional.of(Thread.currentThread().getName() + " have the lock Monitor.").ifPresent(System.out::println);
work();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
booleanLock.unlock();
}
}, name).start();
});
}
private static void work() throws InterruptedException {
Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println);
Thread.sleep(2_000);
}
}
运行效果如下:
可以看到报错了,这里为了印象深刻一下,特意留了个彩蛋,其实上面的代码里我也故意留了彩蛋~~。notifyAll
需要一个monitor
才可以,我们把synchronized
加上运行效果如下:
嗯到这里大体效果实现了,但是细想还是有问题的,如果要你编写一个这个方法的测试用例,你会怎么写呢?
unlock()没做任何限制,我们的测试类是可以随意调用的:
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
booleanLock.unlock();
运行效果如下:
T1线程还工作,T4直接就抢到锁了,那么我们在这个基础上再做一层校验:
private Thread currentThread; //增加一个currentThread代表当前拿到线程的锁
currentThread = Thread.currentThread();
@Override
public synchronized void unlock() {
// 释放锁
if (Thread.currentThread() == currentThread) {
this.initValue = false;
Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println);
this.notifyAll();
}
}
再运行一下:
以这个为主体,增加时间限制来完善我们的Lock:
public static void main(String[] args) throws InterruptedException {
final BooleanLock booleanLock = new BooleanLock();
Stream.of("T1", "T2", "T3", "T4").forEach(name -> {
new Thread(() -> {
try {
booleanLock.lock(2000L);
Optional.of(Thread.currentThread().getName() + " have the lock Monitor.").ifPresent(System.out::println);
work();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Lock.TimeOutException e) {
Optional.of(Thread.currentThread().getName() + " time out.").ifPresent(System.out::println);
} finally {
booleanLock.unlock();
}
}, name).start();
});
}
private static void work() throws InterruptedException {
Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println);
Thread.sleep(5_000);
}
@Override
public void lock(long mills) throws InterruptedException, TimeOutException {
if (mills <= 0) {
lock();
}
long hasRemaining = mills;
long endTime = System.currentTimeMillis() + mills;
while (initValue) {
if (hasRemaining <= 0) {
throw new TimeOutException("Time Out");
}
blockedThreadCollection.add(Thread.currentThread());
this.wait(mills);
hasRemaining = endTime - System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ">>" + hasRemaining);
}
this.initValue = true;
this.currentThread = Thread.currentThread();
}
运行效果如下:
T1线程进来抢到锁,但是需要5秒才能运行结束,其他线程最多等待2秒,所以其他线程先结束等待超时了,T1才释放锁。这个显式锁就先到这,上面代码我故意贴了一些错误的,不知道细心的朋友有没有发现=.=