# 如何实现一个自己的显式锁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才释放锁。这个显式锁就先到这,上面代码我故意贴了一些错误的,不知道细心的朋友有没有发现=.=
- 微服务
- 服务器相关
- 操作系统
- 极客时间操作系统实战笔记
- 01 程序的运行过程:从代码到机器运行
- 02 几行汇编几行C:实现一个最简单的内核
- 03 黑盒之中有什么:内核结构与设计
- Rust
- 入门:Rust开发一个简单的web服务器
- Rust的引用和租借
- 函数与函数指针
- Rust中如何面向对象编程
- 构建单线程web服务器
- 在服务器中增加线程池提高吞吐
- Java
- 并发编程
- 并发基础
- 1.创建并启动线程
- 2.java线程生命周期以及start源码剖析
- 3.采用多线程模拟银行排队叫号
- 4.Runnable接口存在的必要性
- 5.策略模式在Thread和Runnable中的应用分析
- 6.Daemon线程的创建以及使用场景分析
- 7.线程ID,优先级
- 8.Thread的join方法
- 9.Thread中断Interrupt方法学习&采用优雅的方式结束线程生命周期
- 10.编写ThreadService实现暴力结束线程
- 11.线程同步问题以及synchronized的引入
- 12.同步代码块以及同步方法之间的区别和关系
- 13.通过实验分析This锁和Class锁的存在
- 14.多线程死锁分析以及案例介绍
- 15.线程间通信快速入门,使用wait和notify进行线程间的数据通信
- 16.多Product多Consumer之间的通讯导致出现程序假死的原因分析
- 17.使用notifyAll完善多线程下的生产者消费者模型
- 18.wait和sleep的本质区别
- 19.完善数据采集程序
- 20.如何实现一个自己的显式锁Lock
- 21.addShutdownHook给你的程序注入钩子
- 22.如何捕获线程运行期间的异常
- 23.ThreadGroup API介绍
- 24.线程池原理与自定义线程池一
- 25.给线程池增加拒绝策略以及停止方法
- 26.给线程池增加自动扩充,闲时自动回收线程的功能
- JVM
- C&C++
- GDB调试工具笔记
- C&C++基础
- 一个例子理解C语言数据类型的本质
- 字节顺序-大小端模式
- Php
- Php源码阅读笔记
- Swoole相关
- Swoole基础
- php的五种运行模式
- FPM模式的生命周期
- OSI网络七层图片速查
- IP/TCP/UPD/HTTP
- swoole源代码编译安装
- 安全相关
- MySql
- Mysql基础
- 1.事务与锁
- 2.事务隔离级别与IO的关系
- 3.mysql锁机制与结构
- 4.mysql结构与sql执行
- 5.mysql物理文件
- 6.mysql性能问题
- Docker&K8s
- Docker安装java8
- Redis
- 分布式部署相关
- Redis的主从复制
- Redis的哨兵
- redis-Cluster分区方案&应用场景
- redis-Cluster哈希虚拟槽&简单搭建
- redis-Cluster redis-trib.rb 搭建&原理
- redis-Cluster集群的伸缩调优
- 源码阅读笔记
- Mq
- ELK
- ElasticSearch
- Logstash
- Kibana
- 一些好玩的东西
- 一次折腾了几天的大华摄像头调试经历
- 搬砖实用代码
- python读取excel拼接sql
- mysql大批量插入数据四种方法
- composer好用的镜像源
- ab
- 环境搭建与配置
- face_recognition本地调试笔记
- 虚拟机配置静态ip
- Centos7 Init Shell
- 发布自己的Composer包
- git推送一直失败怎么办
- Beyond Compare过期解决办法
- 我的Navicat for Mysql
- 小错误解决办法
- CLoin报错CreateProcess error=216
- mysql error You must reset your password using ALTER USER statement before executing this statement.
- VM无法连接到虚拟机
- Jetbrains相关
- IntelliJ IDEA 笔记
- CLoin的配置与使用
- PhpStormDocker环境下配置Xdebug
- PhpStorm advanced metadata
- PhpStorm PHP_CodeSniffer