同步锁实现

一、背景

在并发场景下,需要单一线程或限定并发数操作某些逻辑,这时候就需要用到一个锁来保证线程安全。

二、思路

  • 使用ConcurrentHashMap实现,但只支持同一个jvm下的线程(暂时满足)
  • 使用Semaphore信号量作为锁
  • 数量操作都使用java原子操作类,例:AtomicInteger、AtomicLong等

三、实操

  1. 构建Key锁对象

    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger; /**
    * key锁对象
    */
    public class ThreadData {
    private String lockKey;
    private Semaphore lock;
    //线程id
    private long threadId;
    //当前线程获取的锁数量
    private AtomicInteger acquireNum; // 第一次加锁时间
    private Long startTime; public ThreadData(String lockKey, long threadId, AtomicInteger acquireNum) {
    this.lockKey = lockKey;
    this.threadId = threadId;
    this.acquireNum = acquireNum;
    this.lock = new Semaphore(acquireNum.get());
    this.startTime = System.currentTimeMillis();
    } public static ThreadData newInstance(String lockKey, int maxNum) {
    return new ThreadData(lockKey, Thread.currentThread().getId(), new AtomicInteger(maxNum));
    } /**
    * 增加当前线程占用锁的数量
    *
    * @return
    */
    public int incrementAcquireNum() {
    return this.acquireNum.incrementAndGet();
    } /**
    * 减少当前线程锁的占有数量
    *
    * @return
    */
    public int decrementAcquireNum() {
    return this.acquireNum.decrementAndGet();
    } public long getThreadId() {
    return threadId;
    } public void setThreadId(long threadId) {
    this.threadId = threadId;
    } public Integer getAcquireNum() {
    return acquireNum.get();
    } public void setAcquireNum(Integer acquireNum) {
    this.acquireNum.set(acquireNum);
    } private Semaphore getLock() {
    return lock;
    } public Long getStartTime() {
    return startTime;
    } /**
    * 获取锁
    */
    public void lock() throws InterruptedException {
    getLock().acquire();
    } /**
    * 在指定时间内获取锁
    *
    * @param waitTimeout
    * @param timeUnit
    */
    public Boolean tryLock(int waitTimeout, TimeUnit timeUnit) throws InterruptedException {
    return getLock().tryAcquire(waitTimeout, timeUnit);
    } /**
    * 只有被当前线程持有锁的情况下才能释放锁
    */
    public void release() {
    lock.release();
    }
    }
  2. 编写同步锁工具类

    import com.jravity.jrlive.utils.StringHelper;
    import lombok.extern.slf4j.Slf4j; import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.TimeUnit; /**
    * 同步锁,ConcurrentHashMap 实现,只支持同一个jvm下的线程
    */
    @Slf4j
    public class KeyLock { private final static ConcurrentHashMap<String, ThreadData> HAPPY_LOCK_MAP = new ConcurrentHashMap<String, ThreadData>();
    private final String lockKey;
    //最大并发数
    private int maxNum; public KeyLock(String lockKey) {
    if (StringHelper.isBlank(lockKey)) {
    throw new NullPointerException("lock key can not be null");
    }
    this.lockKey = lockKey;
    maxNum = 1;
    } public KeyLock(String lockKey, int maxNum) {
    if (StringHelper.isBlank(lockKey)) {
    throw new NullPointerException("lock key can not be null");
    }
    this.lockKey = lockKey;
    this.maxNum = maxNum;
    } /**
    * 获取
    * 规定时间内未获取到返回true
    *
    * @return
    * @throws InterruptedException
    */
    public Boolean acquire(int waitTimeout, TimeUnit timeUnit) {
    ThreadData threadData = ThreadData.newInstance(lockKey, maxNum);
    ThreadData threadExistsValue = HAPPY_LOCK_MAP.putIfAbsent(this.lockKey, threadData);
    if (threadExistsValue == null) {
    threadExistsValue = threadData;
    }
    try {
    return threadExistsValue.tryLock(waitTimeout, timeUnit);
    } catch (InterruptedException e) {
    log.error(String.format("thread was interrupted ,key=%s threadName=%s", this.lockKey, Thread.currentThread().getName()));
    return false;
    }
    } /**
    * 尝试获取锁,不带超时时间
    *
    * @return
    * @throws InterruptedException
    */
    public void acquire() throws InterruptedException {
    ThreadData threadData = ThreadData.newInstance(lockKey, maxNum);
    ThreadData threadExistsValue = HAPPY_LOCK_MAP.putIfAbsent(this.lockKey, threadData);
    if (threadExistsValue == null) {
    threadExistsValue = threadData;
    }
    //重置过期时间
    threadExistsValue.lock();
    } /**
    * 释放
    * 谁加锁谁释放,可以释放多次.
    */
    public void release() {
    ThreadData threadData = HAPPY_LOCK_MAP.get(lockKey);
    if (threadData == null) {
    log.error(String.format("%s 释放数据为空 ", lockKey));
    return;
    }
    threadData.release();
    } public static Map<String, ThreadData> getLockMap() {
    return HAPPY_LOCK_MAP;
    }
    }

    备:可常用ConcurrentHashMap的putIfAbsent()方法来作为是否有key的标准

四、工具类备注

1、Semaphore(信号量)

白话:Semaphore是一间可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限制对于某一资源的同时访问

常见方法:

  • acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。

  • release(释放)实际上会将信号量的值加1,然后唤醒等待的线程

单一JVM同步锁实现的更多相关文章

  1. Java Learning:并发中的同步锁(synchronized)

    引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...

  2. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  3. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

  4. Java同步锁——lock与synchronized 的区别【转】

    在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...

  5. 单例模式(懒汉、饿汉、同步锁、static、枚举)实现

    使用前提: 需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多 三要素: 1.构造方法私有化: 2.实例化的变量引用私有化: 3.获取实例的方法共有. 1.饿汉式单例 弊端:在类装载的时 ...

  6. jvm层面锁优化+一般锁的优化策略

    偏向锁: 首先了解对象头MARK指针(对象头标记,32位): 存储GC标记,对象年龄,对象Hash,锁信息(锁记录的指针,偏向锁线程的ID) 大部分情况是没有竞争的,所以可以通过偏向来提高性能 所谓的 ...

  7. Python并发编程-进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

  8. 同步锁Lock

    用于解决多线程安全问题有三种方式: 同步代码块(隐式锁,基于JVM) 同步方法(隐式锁,基于JVM) 同步锁(显式锁,jdk1.5后出现,相对于前两种方式,更加灵活) 下面通过一段程序来说明一下同步锁 ...

  9. Python3 进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

  10. 同步锁之lock

    一. synchronized的缺陷 当一个代码块被synchronized修饰时,同时该代码块被一个线程执行,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: ...

随机推荐

  1. Prometheus高可用部署

    Prometheus的本地存储给Prometheus带来了简单高效的使用体验,可以让Promthues在单节点的情况下满足大部分用户的监控需求.但是本地存储也同时限制了Prometheus的可扩展性, ...

  2. 【前端必会】webpack 插件,前进路绕不过的障碍

    背景 webpack的使用中我们会遇到各种各样的插件.loader. webpack的功力主要体现在能理解各个插件.loader的数量上.理解的越多功力越深 开始 https://webpack.do ...

  3. Java删除word合并单元格时的重复值

    Spire.Doc提供了Table.applyVerticalMerge()方法来垂直合并word文档里面的表格单元格,Table.applyHorizontalMerge()方法来水平合并表格单元格 ...

  4. nsis使用URLDownloadToFile下载文件

    在Urlmon.dll中有个函数叫URLDownloadToFile,顾名思义,是一个用来下载文件的东西,我们做在线安装或者其他很多时候,往往需要提前下载一些小的文件来配置当前将要安装的程序,遇到这种 ...

  5. 用户输入学号,如果是以aabcddef开头,并且后边是4位数字,前两位大于06小于等于当前年份。判断用户输入是否合法

    package seven_topic; import java.util.*; public class p_19_1 { public static void main(String[] args ...

  6. 这些不知道,别说你熟悉 Spring

    大家好,这篇文章跟大家来聊下 Spring 中提供的常用扩展点.Spring SPI 机制.以及 SpringBoot 自动装配原理,重点介绍下 Spring 基于这些扩展点怎么跟配置中心(Apoll ...

  7. qiankun+vue,为什么我的子应用的子路由老是跳404?这么解决

    主要解决子应用内部跳转路由时,跳到404页的问题 你能搜这个,我姑且认为你基本配置已经好了,而且主跳子的一级路由是正常的,请往下看 忘说了,我的主应用和子应用都是Vue 主应用跳子应用都正常,为什么子 ...

  8. 面向对象day1,创建类和对象时,对象内存是在哪

    上面我创建了两个类,一个是测试类,在测试包里面,一个是生产类,在src文件里面.可以看到,调用类里面的对象的时候需要先创建一个对象,然后通过对象来调用类里面的属性和方法等,但是这个时候我得先记录一下, ...

  9. 题解 CF327A Flipping Game

    前言 数据水的一批,\(\mathcal{O}(n^3)\) 给过我觉得是不应该的. 题意 有一个由 \(0\) 和 \(1\) 组成的序列 \(a_1,a_2,a_3,a_4....,a_n\) . ...

  10. 记录在linux上单机elasticsearch8和kibana8

    目录 1.背景 2.es对jdk和操作系统的要求等 3.安装步骤 3.1 下载对应版本的es 3.2 创建es账户 3.3 修改es配置 3.3.1 修改es配置 3.3.3 修改jvm配置 3.4 ...