Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >高并发编程-自定义带有超时功能的锁

高并发编程-自定义带有超时功能的锁

作者头像
小小工匠
发布于 2021-08-17 02:26:36
发布于 2021-08-17 02:26:36
57600
代码可运行
举报
文章被收录于专栏:小工匠聊架构小工匠聊架构
运行总次数:0
代码可运行

概述

我们知道synchronized的机制有一个很重要的特点是:使用synchronized, 当一个线程获取了锁,其他线程只能一直等待,等待这个获取锁的线程释放锁,如果这个线程执行时间很长,其他线程就需要一直等待 。 除非获取锁的线程执行完了该代码块,释放锁或者线程执行发生异常,JVM会使线程自动释放锁。

当然了J.U.C包中 Doug Lea大神已经设计了非常完美的解决方案,我们这里不讨论J.U.C的实现。

我们自己实现一套的话,该如何实现呢? 有几点需要思考

  1. 原有的synchronized功能,必须保证,即一个线程拿到锁后,其他线程必须等待
  2. 谁加的锁,必须由谁来释放
  3. 加入超时功能

好了,开始吧


步骤

自定义超时异常处理类

既然要设计带超时功能的锁, 少不了当超时时,抛出异常,以便上层捕获处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TimeOutException extends  RuntimeException {

    public TimeOutException(String message){
        super(message);
    }
}

ILock接口

约定几个接口方法: lock 、lock(long timeout)、unlock、getBlockedThread、getBlockedSize 详见代码注释

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.artisan.customLock;

import java.util.Collection;

public interface ILock {

    /**
     * 加锁
     */
    void lock() throws InterruptedException;

    /**
     * 加锁
     * @param timeout 持有锁的时间,过了该时间(毫秒) 自动释放该锁
     */
    void lock(long timeout) throws InterruptedException,TimeOutException;

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 用于观察 有哪些线程因没有获取到锁被blocked
     * @return
     */
    Collection<Thread> getBlockedThreads();

    /**
     * 被blocked的线程数量
     * @return
     */
    int getBlockedSize();

}

实现类

详见代码注释。 加锁和释放锁方法 使用 synchronized 修饰,否则使用wait && notifyAll抛出异常

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;

public class CustomLock implements ILock {

    // 默认false
    // true: 已经被线程抢到  false: 空闲
    private boolean lockFlag;

    // 用于存储被blocked的线程,方便查看及计算被blocked的线程数量
    Collection<Thread> blockedThreadCollection = new ArrayList<>();


    /**
     * 构造函数中初始化该lockFlag
     */
    public CustomLock(){
        this.lockFlag = false;
    }

    /**
     * synchronized 修饰该方法
     * @throws InterruptedException
     */
    @Override
    public synchronized void lock() throws InterruptedException {
        // 如果其他线程已经获取到了锁,让该线程wait
        while(lockFlag){
            // 加入到blockedThreadCollection
            blockedThreadCollection.add(Thread.currentThread());
            // wait
            this.wait();
        }

        // 如果空闲,将该monitor置为true
        blockedThreadCollection.remove(Thread.currentThread());
        lockFlag = true;
    }

    @Override
    public void lock(long timeout) throws InterruptedException, TimeOutException {

    }

    @Override
    public synchronized void unlock() {
        // 如果是加锁的线程
            // 将Monitor置为空闲
            this.lockFlag = false;
            Optional.of(Thread.currentThread().getName() + " 释放lock").ifPresent(System.out::println);
            // 唤醒其他正在等待的线程
            this.notifyAll();
    }

    @Override
    public Collection<Thread> getBlockedThreads() {
        // blockedThreadCollection 可能被其他线程add 或者remove,这里定义为不可变的集合类型
        return Collections.unmodifiableCollection(blockedThreadCollection);
    }

    @Override
    public int getBlockedSize() {
        return blockedThreadCollection.size();
    }
}

测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.artisan.customLock;

import java.time.LocalTime;
import java.util.Optional;
import java.util.stream.Stream;

public class CustomLockTest {


    public static void main(String[] args) throws InterruptedException {
        CustomLock customLock = new CustomLock();

        // 开启5个线程
        Stream.of("T1", "T2", "T3", "T4", "T5")
                .forEach(name -> new Thread(() -> {
                    // 加锁 处理业务
                    try {
                        // 加锁
                        customLock.lock();
                        Optional.of(Thread.currentThread().getName() + " hold the Monitor")
                                .ifPresent(System.out::println);
                        // 调用业务
                        work();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        // 在finally中释放锁
                        customLock.unlock();
                    }
                }, name).start());

    }


    /**
     * 模拟线程的业务逻辑
     *
     * @throws InterruptedException
     */
    public static void work() throws InterruptedException {
        Optional.of(Thread.currentThread().getName() +
                " begin to work " + LocalTime.now().withNano(0)).ifPresent(System.out::println);
        Thread.sleep(3_000);
    }

}

日志输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"E:\Program Files\Java\jdk1.8.0_161\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar=53159:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;D:\IdeaProjects\mvc\target\classes" com.artisan.customLock.CustomLockTest
T1 hold the Monitor
T1 begin to work 22:19:14
T1 释放lock
T5 hold the Monitor
T5 begin to work 22:19:17
T5 释放lock
T2 hold the Monitor
T2 begin to work 22:19:20
T2 释放lock
T4 hold the Monitor
T4 begin to work 22:19:23
T4 释放lock
T3 hold the Monitor
T3 begin to work 22:19:26
T3 释放lock

Process finished with exit code 0

可以看到 确实是一个线程拿到锁后,其他线程必须等待 。

针对第二点呢: 谁加的锁,必须由谁来释放 .

我们来测试下

存在的问题

针对第二点呢: 谁加的锁,必须由谁来释放 .

我们来测试下 : 假设我们在main线程中调用了unlock方法

重新运行测试,观察日志

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
T1 hold the Monitor
T1 begin to work 22:24:41
main 释放lock
T5 hold the Monitor
T5 begin to work 22:24:41
T1 释放lock
T2 hold the Monitor
T2 begin to work 22:24:44
T5 释放lock
T4 hold the Monitor
T4 begin to work 22:24:44
T2 释放lock
T3 hold the Monitor
T3 begin to work 22:24:47
T4 释放lock
T3 释放lock

Process finished with exit code 0

T1拿到锁还没有工作完,就被主线程释放了,结果T5又抢到了… 很明显不对了 。

修复存在的问题

见代码

再次运行测试 ,OK


超时功能

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 @Override
    public synchronized  void lock(long timeout) throws InterruptedException, TimeOutException {
        // 入参不合理,直接调用lock ,也可抛出异常
        if (timeout <= 0 ) lock();

        // 线程等待的剩余时间
        long  leftTime = timeout;
        // 计算结束时间
        long endTime = System.currentTimeMillis() + timeout;

        while(lockFlag){
            // 如果超时了,抛出异常
            if (leftTime <= 0){
                throw new TimeOutException(Thread.currentThread().getName() + " 超时...");
            }

            // 加入到blockedThreadCollection
            blockedThreadCollection.add(Thread.currentThread());
            // wait 指定的时间
            this.wait(timeout);
            // 计算是否超时
            leftTime = endTime - System.currentTimeMillis();
        }

        // 如果空闲,将该monitor置为true
        blockedThreadCollection.remove(Thread.currentThread());
        this.lockFlag = true;
        // 将当前线程置为lockHolderThread
        this.lockHolderThread = Thread.currentThread();

    }

测试超时功能

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.artisan.customLock;

import java.time.LocalTime;
import java.util.Optional;
import java.util.stream.Stream;

public class CustomLockTest {


    public static void main(String[] args) {
        CustomLock customLock = new CustomLock();

        // 开启5个线程
        Stream.of("T1", "T2", "T3", "T4", "T5")
                .forEach(name -> new Thread(() -> {
                    // 加锁 处理业务
                    try {
                        // 加锁 最多等待100毫秒,如果100ms,没抢到则中断执行
                        customLock.lock(100);
                        Optional.of(Thread.currentThread().getName() + " hold the Monitor")
                                .ifPresent(System.out::println);
                        // 调用业务
                        work();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (TimeOutException e){
                        Optional.of(Thread.currentThread().getName() + " timeOut")
                                .ifPresent(System.out::println);
                    }finally {
                        // 在finally中释放锁
                        customLock.unlock();
                    }
                }, name).start());


    }


    /**
     * 模拟线程的业务逻辑
     *
     * @throws InterruptedException
     */
    public static void work() throws InterruptedException {
        Optional.of(Thread.currentThread().getName() +
                " begin to work " + LocalTime.now().withNano(0)).ifPresent(System.out::println);
        Thread.sleep(3_000);
    }

}

运行结果:

OK。


CustomLock

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.artisan.customLock;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;

public class CustomLock implements ILock {

    // 默认false
    // true: 已经被线程抢到  false: 空闲
    private boolean lockFlag;

    // 用于存储被blocked的线程,方便查看及计算被blocked的线程数量
    Collection<Thread> blockedThreadCollection = new ArrayList<>();

    // 当前持有锁的线程
    Thread lockHolderThread ;
    /**
     * 构造函数中初始化该lockFlag
     */
    public CustomLock(){
        this.lockFlag = false;
    }

    /**
     * synchronized 修饰该方法
     * @throws InterruptedException
     */
    @Override
    public synchronized void lock() throws InterruptedException {
        // 如果其他线程已经获取到了锁,让该线程wait
        while(lockFlag){
            // 加入到blockedThreadCollection
            blockedThreadCollection.add(Thread.currentThread());
            // wait
            this.wait();
        }

        // 如果空闲,将该monitor置为true
        blockedThreadCollection.remove(Thread.currentThread());
        this.lockFlag = true;
        // 将当前线程置为lockHolderThread
        this.lockHolderThread = Thread.currentThread();
    }

    @Override
    public synchronized  void lock(long timeout) throws InterruptedException, TimeOutException {
        // 入参不合理,直接调用lock ,也可抛出异常
        if (timeout <= 0 ) lock();

        // 线程等待的剩余时间
        long  leftTime = timeout;
        // 计算结束时间
        long endTime = System.currentTimeMillis() + timeout;

        while(lockFlag){
            // 如果超时了,抛出异常
            if (leftTime <= 0){
                throw new TimeOutException(Thread.currentThread().getName() + " 超时...");
            }

            // 加入到blockedThreadCollection
            blockedThreadCollection.add(Thread.currentThread());
            // wait 指定的时间
            this.wait(timeout);
            // 计算是否超时
            leftTime = endTime - System.currentTimeMillis();
        }

        // 如果空闲,将该monitor置为true
        blockedThreadCollection.remove(Thread.currentThread());
        this.lockFlag = true;
        // 将当前线程置为lockHolderThread
        this.lockHolderThread = Thread.currentThread();

    }

    @Override
    public synchronized void unlock() {
        // 如果是加锁的线程
        if(lockHolderThread == Thread.currentThread()){
            // 将Monitor置为空闲
            this.lockFlag = false;
            Optional.of(Thread.currentThread().getName() + " 释放lock" +  LocalTime.now().withNano(0)).ifPresent(System.out::println);
            // 唤醒其他正在等待的线程
            this.notifyAll();
        }
    }

    @Override
    public Collection<Thread> getBlockedThreads() {
        // blockedThreadCollection 可能被其他线程add 或者remove,这里定义为不可变的集合类型
        return Collections.unmodifiableCollection(blockedThreadCollection);
    }

    @Override
    public int getBlockedSize() {
        return blockedThreadCollection.size();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/10/13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
join方法的使用
使所属的线程对象x正常执行run()方法中的任务,而使当前线程y无限期的阻塞,直到x线程销毁后再继续执行线程y后面的代码。
全栈程序员站长
2022/06/29
6130
高并发编程-线程生产者消费者的综合示例
需求: 假设有10个线程,最多同时运行5个 要求: 不使用线程池,使用synchronized-wait¬ifyAll机制
小小工匠
2021/08/17
2170
高并发编程-自定义简易的线程池(2),体会原理
高并发编程-自定义简易的线程池(1),体会原理 中只实现了任务队列,我们这里把其余的几个也补充进来
小小工匠
2021/08/17
2270
高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析
3. 紧接着,P1又抢到了锁,但是生产后没有被消费,所以直接进入LOCK.wati. 执行完以后释放锁。P1-----WAITING . 对应日志
小小工匠
2021/08/17
3460
高并发编程-线程通信_使用wait和notify进行线程间的通信
约定,Worker A 生产货物到工作台上, Workder B 从工作台 取走(消费)货物。
小小工匠
2021/08/17
3620
java并发编程实战wwj———————-第一阶段————–27-28-29-30
sleep:是Thread的方法,sleep不释放锁,sleep不用synchronized,不需要被唤醒。
全栈程序员站长
2022/11/10
2110
java并发编程实战wwj———————-第一阶段————–27-28-29-30
高并发编程系列
放在静态方法上面,由于静态没有this可以锁定,不需要new 出对象,运用了反射.
后端码匠
2019/10/09
3700
多线程基础(八):ReentrantLock的使用及与synchronized的区别
还记得,前面一篇文章《什么?面试官让我用ArrayList实现一个阻塞队列?》中,描述了一个关于实现两个线程交替打印以及实现阻塞队列的例子,那么今天,我们来看看另外一种解决办法—ReentrantLock。
冬天里的懒猫
2020/09/14
6000
用Java实现JVM第五章《指令集和解释器》
本案例通过java代码实现jvm规范中指令集和解释器,完成后就可以开始执行1到100的加和计算。
小傅哥
2020/01/19
6252
用Java实现JVM第五章《指令集和解释器》
高并发编程-深入分析wait和sleep的区别并结合源码示例佐证
wait和sleep的区别,这个确实是面试中非常常见的一道题目,这里我们通过源码并结合示例来一起加深下对wait和sleep的理解 。
小小工匠
2021/08/17
3880
【多线程】之线程通讯wait和notify的使用
1、定义 等待/通知机制,是指一个线程A调用了对象object的wait()方法进入等待状态,而另一个线程B调用了对象object的notify或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而还行后续操作。 使用wait和notify方法实现线程之间的通信,这两个方法是Object类的方法。 注意细节: 1.1 调用wait()方法,会释放锁,线程状态由RUNNING->WAITNG,当前线程进入对象等待; 1.2 调用notify()/notifyAll()方法不会立马释放锁,notify()方法是将等待队列中的线程移到同步队列中,而notifyAll()则是全部移到同步队列中, 被移出的线程状态WAITING-->BLOCKED; 重点注意,等待队列和同步队列的转换;wait()后进入等待队列;notify()/notifyAll(),线程进入同步队列; 1.3 当前调用notify()/notifyAll()的线程释放锁了才算释放锁,才有机会唤醒wait线程; 1.4 从wait()返回的前提是必须获得调用对象锁,也就是说notify()与notifyAll()释放锁之后,wait()进入BLOCKED状态,如果其他线程 有竞争当前锁的话,wait线程继续争取锁资格。可以理解为,从同步队列中的线程抢占锁执行; 1.5 使用wait()、notify()、notifyAll()方法时需要先调对象加锁。这就是跟synchronized关键字配置使用; 2、代码运行过程
用户5640963
2021/06/09
3980
JAVA并发之加锁导致的活跃性问题剖析
但是加锁也为我们带来了诸多问题 如:死锁,活锁,线程饥饿等问题 这一章我我们主要处理锁带来的问题. 首先就是最出名的死锁
Java宝典
2020/12/04
1.1K0
JVM-白话聊一聊JVM类加载和双亲委派机制源码解析
其中最核心的方法 loadClass ,其实现我们常说的双亲委派机制 ,我们后面展开。
小小工匠
2021/08/17
2830
Java并发编程之ReentrantLock
如:线程1获取A对象锁, 线程2获取B对象锁; 此时线程1又想获取B对象锁, 线程2又想获取A对象锁; 它们都等着对象释放锁, 此时就称为死锁
冬天vs不冷
2025/01/21
680
Java并发编程之ReentrantLock
Java高并发与多线程网络编程
当JVM启动时,会创建一个非守护线程 main,作为整个程序的入口,以及多个与系统相关的守护线程。
devi
2021/08/19
1.4K0
Java高并发与多线程网络编程
用Java实现JVM第六章《类和对象》
本案例通过java代码实现jvm规范中指令集和解释器,完成后就可以开始执行1到100的加和计算。
小傅哥
2020/01/19
3970
用Java实现JVM第六章《类和对象》
高并发编程-CyclicBarrier深入解析
CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(也可以叫同步点),即相互等待的线程都完成调用await方法,所有被屏障拦截的线程才会继续运行await方法后面的程序。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrier很有用。因为该屏障点在释放等待线程后可以重用,所以称它为循环的屏障点。CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程到达屏障点之后(但在释放所有线程之前),该命令只在所有线程到达屏障点之后运行一次,并且该命令由最后一个进入屏障点的线程执行。
JavaQ
2018/08/15
1.9K0
Java并发-定义锁以及消费者-生产者模式实现
 对于Java并发的锁结构,我们常常使用的是synchonized结构,而由于其灵活度较低,所以在Java-5后提出了Lock接口,以及AbstractQueuedSynchronizer抽象类供我们方便且安全地来实现自定义锁结构,下面从代码出发来开始这篇文章的阅读。
Fisherman渔夫
2020/02/17
5880
并发编程中的金光咒-锁(基础版)
大家好,我是小高先生。在Java并发编程的世界中,锁的地位至关重要。它就像是一道坚固的防线,确保了并发编程运行结果的正确性。你可以不准备攻击装备,但是锁这个防御装备是必不可少的。相信大家在之前都对锁或多或少有些了解,本文将带领大家学习锁的基础知识。
小高先生
2024/02/17
1420
Java Review - 并发编程_ 回环屏障CyclicBarrier原理&源码剖析
Java Review - 并发编程_ CountDownLatch原理&源码剖析介绍的CountDownLatch在解决多个线程同步方面相对于调用线程的join方法已经有了不少优化,但是CountDownLatch的计数器是一次性的,也就是等到计数器值变为0后,再调用CountDownLatch的await和countdown方法都会立刻返回,这就起不到线程同步的效果了。
小小工匠
2021/12/30
2820
Java Review - 并发编程_ 回环屏障CyclicBarrier原理&源码剖析
推荐阅读
相关推荐
join方法的使用
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验