💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 如何实现一个自己的显式锁Lock 首先我们通过代码引出一个问题:如果方法上了同步锁并且被一个线程占有了,那么其他线程想执行这个方法就要一直等待下去,如果这个方法死循环或者无限执行下去,等待的线程就会无限等待下去,这里来演示一下这种场景: ```java /** * @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) { //一直执行下去。。。 } } } ``` 运行效果如下: ![](https://img.kancloud.cn/f6/7c/f67c5c9821d633e66bd097a4003ba80c_1154x227.gif) 使用interrupt()打断一下T2线程: ```java 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) { //一直执行下去 } } } ``` 运行效果如下: ![](https://img.kancloud.cn/0e/87/0e87ccd3662a5e1594b5b2a7a13f5acd_1154x227.gif) 因为同步方法里有while循环,接收不到中断异常的,所以基于这个场景我们来定义一个带有超时功能的锁,思路还是和上一个案例差不多,一个while一直检测lock,再结合wait(),notifyAll(),代码如下: 我们先定义一个Lock interface: ```java 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(); } ``` 定义一个类来实现接口: ```java 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`来存放阻塞的线程。 ```java private boolean initValue; private Collection<Thread> blockedThreadCollection = new ArrayList<>(); public BooleanLock() { this.initValue = true; } ``` 完善lock方法: ```java @Override public synchronized void lock() throws InterruptedException { // 锁已经被其他线程使用 while (initValue) { blockedThreadCollection.add(Thread.currentThread()); this.wait(); } // 锁未被使用,抢到锁立即设置initValue的值 this.initValue = true; blockedThreadCollection.remove(Thread.currentThread()); } ``` 这里和上一篇的数据采集很相似。 完善unlock方法: ```java @Override public void unlock() { this.initValue = false; Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println); this.notifyAll(); } ``` 查询方法完善如下: ```java @Override public Collection<Thread> getBlockedThread() { return Collections.unmodifiableCollection(blockedThreadCollection); } @Override public int getBlockedSize() { return blockedThreadCollection.size(); } ``` `return`的`Collections`一定使用`unmodifiableCollection`因为直接返回的是一个实例,调用者可以随意更改。所以我们把他置为·`unmodifiableCollection`。 测试一下代码: ```java 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); } } ``` 运行效果如下: ![](https://img.kancloud.cn/60/f1/60f16f4413126be637a00455cae41d5e_1154x227.gif) 可以看到报错了,这里为了印象深刻一下,特意留了个彩蛋,其实上面的代码里我也故意留了彩蛋~~。`notifyAll`需要一个`monitor`才可以,我们把`synchronized`加上运行效果如下: ![](https://img.kancloud.cn/3f/64/3f6430ffd6fe63830d5c807b67310873_1154x227.gif) 嗯到这里大体效果实现了,但是细想还是有问题的,如果要你编写一个这个方法的测试用例,你会怎么写呢? unlock()没做任何限制,我们的测试类是可以随意调用的: ```java try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } booleanLock.unlock(); ``` 运行效果如下: ![](https://img.kancloud.cn/c9/d1/c9d16cebcfcefde2769afc214d2baa8c_1082x227.gif) T1线程还工作,T4直接就抢到锁了,那么我们在这个基础上再做一层校验: ```java private Thread currentThread; //增加一个currentThread代表当前拿到线程的锁 ``` ```java currentThread = Thread.currentThread(); ``` ```java @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(); } } ``` 再运行一下: ![](https://img.kancloud.cn/6d/77/6d77ba5298a0d85be3e5dc422e850f11_1082x227.gif) 以这个为主体,增加时间限制来完善我们的Lock: ```java 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(); }); } ``` ```java private static void work() throws InterruptedException { Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println); Thread.sleep(5_000); } ``` ```java @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(); } ``` 运行效果如下: ![](https://img.kancloud.cn/3b/9d/3b9d9377dcb7787908e232af4139e312_1082x227.gif) T1线程进来抢到锁,但是需要5秒才能运行结束,其他线程最多等待2秒,所以其他线程先结束等待超时了,T1才释放锁。这个显式锁就先到这,上面代码我故意贴了一些错误的,不知道细心的朋友有没有发现=.=