# 如何實現一個自己的顯式鎖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) {
//一直執行下去。。。
}
}
}
```
運行效果如下:

使用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) {
//一直執行下去
}
}
}
```
運行效果如下:

因為同步方法里有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);
}
}
```
運行效果如下:

可以看到報錯了,這里為了印象深刻一下,特意留了個彩蛋,其實上面的代碼里我也故意留了彩蛋~~。`notifyAll`需要一個`monitor`才可以,我們把`synchronized`加上運行效果如下:

嗯到這里大體效果實現了,但是細想還是有問題的,如果要你編寫一個這個方法的測試用例,你會怎么寫呢?
unlock()沒做任何限制,我們的測試類是可以隨意調用的:
```java
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
booleanLock.unlock();
```
運行效果如下:

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();
}
}
```
再運行一下:

以這個為主體,增加時間限制來完善我們的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();
}
```
運行效果如下:

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