架构(十三)动态本地锁

news2024/11/14 18:23:44

一、引言

        加锁大家都知道,但是目前提供动态锁的基本都是分布式锁,根据订单或者某个收费款项进行加锁。比如这个1订单要收刷卡费用,那就OREDER_1做为key丢到redis进行分布式加锁。这也是当下分布式锁最流行的方式。

        但是对于平台项目或者一些并发程度低的场景,分布式锁就没有必要了,本地锁更加方便。但是本地锁只有synchronized、ReentrantLock之类的方式,想动态的加锁只用他们是实现不了的。

二、实现

        那么如果要实现动态的本地锁怎么做呢?

        先看看redis的分布式锁是怎么做的,他其实就是利用redis的单线程往里面存一个值,如果已经有线程存了,并发的线程就存不进去,只能等。只不过各个redis的客户端还要考虑删除的并发性、锁超时删除、加锁等待这些问题。

1、ConcurrentHashMap

        借鉴这个方案,本地也可以加个存储,为了并发的可读性使用ConcurrentHashMap,这里可以有效的避免其他线程解锁删除缓存

private static final ConcurrentHashMap<String, String> map = 
new ConcurrentHashMap<>();

        加锁就把OREDER_1塞到map里面塞的过程需要防止并发,所以使用synchronized之类的就可以,因为map塞数据可比业务执行的加锁时间短多了

private synchronized static boolean getLock(String key) {
        // map里面塞一下很快,可以使用synchronized
        if (map.containsKey(key)) {
            // 懒汉模式,再判断一遍,免得两个线程一起通过了外层的判断
            return false;
        }
        map.put(key,key);
        return true;
    }

        加锁的方法就是先判断一下有没有已经占了位置的,没有就往map里面占位置

public static boolean tryLock(String key, long timeout, TimeUnit unit) {
        if (map.containsKey(key)) {
            return false;
        }
        return getLock(key);
    }

        解锁就是直接删除

public static void unLock(String key) {
        if (!map.containsKey(key)) {
            return;
        }
        // 释放锁
        map.remove(key);
    }

        这是最简单的做法,那么上面的实现有什么问题呢?最大的问题就是删除的时候可能被其他线程给删了,毕竟不会所有人都按照预想的去使用工具,安全是架构应该考虑的。

        还有锁的超时、等待多长时间没有锁就失败两个功能点

2、优化解锁、锁超时

       要优化这个实现就可以结合ReentrantLock,他有判断是否本线程加锁和等待多长时间进行加锁的api,如果自己实现相当于把他里面的线程存储和睡眠唤醒给重复做一遍,没有必要。

        那么怎么用它呢,map的value存储一个lock,相当于一个key一个lock,生成和删除的时候可以使用synchronized,这个和刚刚说的一样,map里面塞一个删一个是很快的,new一个lock和lock.unlock也是很快的,主要的时间都在业务处理和同一场景下不同单号之间的阻塞

public class LockKeyUtil {

    private static final ConcurrentHashMap<String, ReentrantLock> map = new ConcurrentHashMap<>();

    /**
     * 从map里获取锁 如果存在则返回 不存在则创建
     *
     * @param key key
     */
    private synchronized static ReentrantLock getReentrantLock(String key) {
        if (map.containsKey(key)) {
            return map.get(key);
        }
        return map.compute(key, (k, lock) -> {
            lock = new ReentrantLock();
            return lock;
        });
    }

    /**
     * 
     * @param key
     * @param waitTimeout
     * @param unit
     * @return
     */
    public static boolean tryLock(String key, long waitTimeout, TimeUnit unit) {
        ReentrantLock lock = getReentrantLock(key);
        boolean res;
        try {
            res = lock.tryLock(waitTimeout, unit);
        } catch (InterruptedException e) {
            unLock(key);
            res = false;
        }
        return res;
    }

    /**
     * 释放锁
     *
     * @param key key
     */
    public static synchronized void unLock(String key) {
        if (!map.containsKey(key)) {
            return;
        }
        ReentrantLock lock = map.get(key);
        // 释放锁
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
            map.remove(key);
        }
    }

}

3、优化加锁队列

        上面的实现可以看到还有一个问题,如果在tryLock的时候,多个线程进入了,那么第一个线程解锁的时候把他移除map就有问题了,所以unlock还可以根据加锁队列优化一下

        通过getQueueLength知道有没有在等待加锁的线程,当然了这三行代码不是原子性的,所以是有可能刚刚取完队列还没删除map之前就有线程去加锁了,但是这种情况并发几率可以说是万分之一不到,可以不考虑

public synchronized static void unLock(String key) {
        if (!map.containsKey(key)) {
            return;
        }
        ReentrantLock lock = map.get(key);
        // 释放锁
        if (lock.isHeldByCurrentThread()) {
            int wait = lock.getQueueLength();
            lock.unlock();
            if (wait == 0) {
                map.remove(key);
            }
        }
    }

4、执行超时自动解锁

        这里还有一个执行超时自动解锁的功能,其实感觉没必要,用的时候一般都是在finally里面去unlock,所以几乎不会有不解锁的情况

String key = LOCK + request.getId();
        boolean locked = LockKeyUtil.tryLock(key, 3, TimeUnit.SECONDS);
        if (!locked) {
            throw new OrderException("LOCK_FAIL");
        }
        try {
            //业务处理
            
        } finally {
            LockKeyUtil.unLock(key);
        }

       

三、测试

1、相同key测试代码

        这段代码主要是让线程1或者3先拿到锁,那么其中一个和3就一定处于等待状态,如果是3在等,他到最后也抢不到锁

public static void main(String[] args) {
        String key = "order_1666";
        Thread thread1 = new Thread(() -> {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String formattedDateTime = now.format(formatter);
            System.out.println("thread1:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
            boolean res = LockKeyUtil.tryLock(key, 2, TimeUnit.SECONDS);
            System.out.println("thread1:" + Thread.currentThread().getName() + " lock res:" + res);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockKeyUtil.unLock(key, true);
            now = LocalDateTime.now();
            formattedDateTime = now.format(formatter);
            System.out.println("thread1:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
        });
        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1);
                LocalDateTime now = LocalDateTime.now();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                String formattedDateTime = now.format(formatter);
                System.out.println("thread2:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
                boolean res = LockKeyUtil.tryLock(key, 5, TimeUnit.SECONDS);
                System.out.println("thread2:" + Thread.currentThread().getName() + " lock res:" + res);
                Thread.sleep(5000);
                LockKeyUtil.unLock(key, true);
                now = LocalDateTime.now();
                formattedDateTime = now.format(formatter);
                System.out.println("thread2:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread3 = new Thread(() -> {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String formattedDateTime = now.format(formatter);
            System.out.println("thread3:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
            boolean res = LockKeyUtil.tryLock(key, 1, TimeUnit.SECONDS);
            System.out.println("thread3:" + Thread.currentThread().getName() + " lock res:" + res);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockKeyUtil.unLock(key, true);
            now = LocalDateTime.now();
            formattedDateTime = now.format(formatter);
            System.out.println("thread3:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
        });
        thread1.start();
        thread2.start();
        thread3.start();
    }

 

2、相同key测试结果

        结果和预期相符,线程1先抢到了,执行完之后线程2 抢到,线程3在过程中就失败了

        整个执行链路也给打出来了

thread1:Thread-0 start:2024-02-06 13:59:12
thread3:Thread-2 start:2024-02-06 13:59:12
thread2:Thread-1 start:2024-02-06 13:59:12
thread:Thread-0 tryLock key:order_1666 start
thread:Thread-2 tryLock key:order_1666 start
thread:Thread-1 tryLock key:order_1666 start
thread:Thread-0 getReentrantLock key:order_1666 start
thread:Thread-0 getReentrantLock key:order_1666 new ReentrantLock()
thread:Thread-1 getReentrantLock key:order_1666 start
thread:Thread-0 tryLock key:order_1666res:true
thread1:Thread-0 lock res:true
thread:Thread-1 getReentrantLock key:order_1666 containsKey
thread:Thread-2 getReentrantLock key:order_1666 start
thread:Thread-2 getReentrantLock key:order_1666 containsKey
thread:Thread-2 tryLock key:order_1666res:false
thread3:Thread-2 lock res:false
thread:Thread-0 unLock key:order_1666 unlock success
thread:Thread-2 unLock key:order_1666 not isHeldByCurrentThread
thread:Thread-1 tryLock key:order_1666res:true
thread2:Thread-1 lock res:true
thread1:Thread-0 end:2024-02-06 13:59:14
thread3:Thread-2 end:2024-02-06 13:59:14
thread:Thread-1 unLock key:order_1666 unlock success
thread:Thread-1 unLock key:order_1666 map remove
thread2:Thread-1 end:2024-02-06 13:59:19

3、不同key测试

        就是多加一个key去加锁解锁,看能不能同时加同时解        

public static void main(String[] args) {
        String key = "order_1666";
        Thread thread1 = new Thread(() -> {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String formattedDateTime = now.format(formatter);
            System.out.println("thread1:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
            boolean res = LockKeyUtil.tryLock(key, 2, TimeUnit.SECONDS);
            System.out.println("thread1:" + Thread.currentThread().getName() + " lock res:" + res);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockKeyUtil.unLock(key, true);
            now = LocalDateTime.now();
            formattedDateTime = now.format(formatter);
            System.out.println("thread1:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
        });
        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1);
                LocalDateTime now = LocalDateTime.now();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                String formattedDateTime = now.format(formatter);
                System.out.println("thread2:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
                boolean res = LockKeyUtil.tryLock(key, 5, TimeUnit.SECONDS);
                System.out.println("thread2:" + Thread.currentThread().getName() + " lock res:" + res);
                Thread.sleep(5000);
                LockKeyUtil.unLock(key, true);
                now = LocalDateTime.now();
                formattedDateTime = now.format(formatter);
                System.out.println("thread2:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        String key3 = "order_1999";
        Thread thread3 = new Thread(() -> {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String formattedDateTime = now.format(formatter);
            System.out.println("thread3:" + Thread.currentThread().getName() + " start:" + formattedDateTime);
            boolean res = LockKeyUtil.tryLock(key3, 1, TimeUnit.SECONDS);
            System.out.println("thread3:" + Thread.currentThread().getName() + " lock res:" + res);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockKeyUtil.unLock(key3, true);
            now = LocalDateTime.now();
            formattedDateTime = now.format(formatter);
            System.out.println("thread3:" + Thread.currentThread().getName() + " end:" + formattedDateTime);
        });
        thread1.start();
        thread2.start();
        thread3.start();
    }

        结果是不同的key可以同时加锁的,这就实现了锁的动态性

thread2:Thread-1 start:2024-02-06 14:06:28
thread1:Thread-0 start:2024-02-06 14:06:28
thread3:Thread-2 start:2024-02-06 14:06:28
thread3:Thread-2 lock res:true
thread2:Thread-1 lock res:true
thread3:Thread-2 end:2024-02-06 14:06:29
thread1:Thread-0 lock res:false
thread1:Thread-0 end:2024-02-06 14:06:32
thread2:Thread-1 end:2024-02-06 14:06:33

四、总结        

        最后的实现就是这样了

f6d008a35cc144f89dec2df89714664d.png

        看起来很简单的功能,要考虑的东西其实很多,这种实现也不是唯一的,有兴趣可以跟作者讨论其他的实现方案

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1436945.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

飞天使-k8s知识点14-kubernetes散装知识点3-Service与Ingress服务发现控制器

文章目录 Service与Ingress服务发现控制器存储、配置与角色 Service与Ingress服务发现控制器 在 Kubernetes 中&#xff0c;Service 和 Ingress 是两种不同的资源类型&#xff0c;它们都用于处理网络流量&#xff0c;但用途和工作方式有所不同。Service 是 Kubernetes 中的一个…

redis:七、集群方案(主从复制、哨兵模式、分片集群)和面试模板

redis集群方案 在Redis中提供的集群方案总共有三种&#xff08;一般一个redis节点不超过10G内存&#xff09; 主从复制哨兵模式分片集群 主从复制&#xff08;主从数据同步&#xff09; replid和offset Replication Id&#xff1a;简称replid&#xff0c;是数据集的标记&a…

回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测&#xff08;完整源码…

Java LinkedList 实现栈和队列

Java LinkedList 实现栈和队列 package com.zhong.collection;import java.util.LinkedList;public class LinkedListDemo {public static void main(String[] args) {// LinkedList 创建一个队列LinkedList<String> queue new LinkedList<>();// 进队System.out…

大华 DSS 数字监控系统 attachment_getAttList.action SQL 注入漏洞复现

0x01 产品简介 大华 DSS 数字监控系统是大华开发的一款安防视频监控系统,拥有实时监视、云台操作、录像回放、报警处理、设备管理等功能。 0x02 漏洞概述 大华 DSS存在SQL注入漏洞,攻击者 /portal/attachment_getAttList.action 路由发送特殊构造的数据包,利用报错注入获…

git合入的parents和child

最近在管理代码&#xff0c;有2的权限&#xff0c;看到一些以前1看不到的东西。 有时候会遇到多个人基于同一节点提交代码&#xff0c;那就要选择先合入和后合入&#xff0c;如果这多人修改到同一个文件同一个地方&#xff0c;就可能产生冲突&#xff0c;一般要避免这种情况出…

NLP_神经概率语言模型(NPLM)

文章目录 NPLM的起源NPLM的实现1.构建实验语料库2.生成NPLM训练数据3.定义NPLM4.实例化NPLM5.训练NPLM6.用NPLM预测新词 NPLM小结 NPLM的起源 在NPLM之前&#xff0c;传统的语言模型主要依赖于最基本的N-Gram技术&#xff0c;通过统计词汇的共现频率来计算词汇组合的概率。然而…

Stata学习(1)

一、五大窗口 Command窗口&#xff1a;实现人机交互 来导入一个自带数据&#xff1a; sysuse是导入系统自带的数据&#xff0c;auto导入该数据的名称&#xff0c;后面的clear是清除之前的数据 结果窗口&#xff1a;展示计算结果、查找功能 在Edit的find可以实现查找功能&#…

如何使用C#调用LabVIEW算法

新建一个工程 这是必须的&#xff1b; 创建项目 项目 点击完成&#xff1b; 将项目另存为&#xff1b;方便后续的使用&#xff1b; 创建 一个测试VI 功能很简单&#xff0c;用的一个加法&#xff1b;将加数A&#xff0c;B设置为输入&#xff0c;和C设置为输出&#xff0c;…

Spring Boot项目中解决跨域问题(四种方式)

目录 一&#xff0c;跨域产生的原因二&#xff0c;什么情况下算跨域三&#xff0c;实际演示四&#xff0c;解决跨域的方法1&#xff0c;CrossOrigin注解2&#xff0c;添加全局过滤器3&#xff0c;实现WebMvcConfigurer4&#xff0c;Nginx解决跨域5&#xff0c;注意 开发项目的时…

黑马Java——集合进阶(List、Set、泛型、树)

一、集合的体系结构 1、单列集合&#xff08;Collection&#xff09; 二、Collection集合 1、Collection常见方法 1.1代码实现&#xff1a; import java.util.ArrayList; import java.util.Collection;public class A01_CollectionDemo1 {public static void main(String[] a…

Android实现底部导航栏方法(Navigation篇)

Navigation实现底部导航栏 前言导入和基本使用导入基础使用创建nav文件编辑Nav文件添加页面&#xff08;代码版&#xff09;添加页面&#xff08;图解版&#xff09; 创建导航动作 action创建action&#xff08;代码版&#xff09;创建action&#xff08;图解版&#xff09; 编…

Linux的进程信号

注意&#xff1a;首先需要提醒一个事情&#xff0c;本节提及的进程信号和下节的信号量没有任何关系&#xff0c;请您区分对待。 1.信号概念 1.1.生活中的信号 我们在生活中通过体验现实&#xff0c;记忆了一些信号和对应的处理动作&#xff0c;这意味着信号有以下相关的特点&…

上位机图像处理和嵌入式模块部署(统计函数执行时间)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和pc上位机相比较&#xff0c;嵌入式设备的计算资源很多时候都是不足的。但是&#xff0c;嵌入式设备胜在稳定性和成本上面&#xff0c;这方面又是…

【XR806开发板试用】xr806使用tcp socket与手机通信

本文为极术社区XR806开发板活动试用文章。 参考&#xff1a;基于星辰处理器的全志XR806开源鸿蒙开发板上手体验 搭建环境。并成功编译。 项目源码 &#xff1a; https://gitee.com/kingwho/smart-home 在同一个局域网中&#xff0c;手机与xr806连接后&#xff0c;手机 APP 每隔…

lnmp一键安装包+wordpress

理论知识 1. LNMP组成介绍​ LNMP代表的是Linux系统下NginxMySQLPHP组成的动态网站系统解决方案。如图所示&#xff0c;Linux是目前最流行的免费操作系统&#xff1b;Nginx性能稳定、功能丰富、处理静态文件速度快且消耗系统的资源极少&#xff1b;MySQL是一个性能卓越、服务稳…

手拉手Vue3+vite引入echarts

技术栈springboot3hutool-alloshi-coreVue3viteechartsTailwindCSS软件版本IDEAIntelliJ IDEA 2022.2.1JDK17Spring Boot3.1hutool-all5.8.18oshi-core6.4.1Vue35.0.10vite5.0.10axios1.6.7echarts5.4.3 ECharts是一个使用 JavaScript 实现的开源可视化库&#xff0c;可以流畅…

awd总结

总结&#xff1a; 由于是第一次参加AWD比赛&#xff0c;各方面经验都不足&#xff0c;在参赛的前几天也是疯狂搜集各种脚本、框架、工具等&#xff0c;同时也参考b站的视频进行学习&#xff0c;我发现就是还是实操才能更快的学习 我觉得就是我前期的准备工作不足&#xff0c;…

stm32软件安装以及创建工程

文章目录 前言一、软件安装软件破解 二、创建工程三、创建项目创建组配置启动文件添加到组 为项目添加头文件路径创建源文件&#xff08;main函数文件&#xff09;使用寄存器配置引脚拼接好STLINK与stm32最小电路板的接线编写程序配置STLink下载程序配置寄存器配置13号端口&…

智能化运维发展现状?智能化运维方向有哪些?

智能运维方向主要包括人工运维、自动运维和智能运维三个阶段。从以下几个方面可以简要介绍智能运维的发展情况&#xff1a;  市场参与者众多&#xff1a;我国智能运维领域参与者众多&#xff0c;市场份额相对较低。华为、浪潮云、联想等硬件制造商在市场上占有很大份额。  …