单一JVM同步锁实现
同步锁实现
一、背景
在并发场景下,需要单一线程或限定并发数操作某些逻辑,这时候就需要用到一个锁来保证线程安全。
二、思路
- 使用ConcurrentHashMap实现,但只支持同一个jvm下的线程(暂时满足)
- 使用Semaphore信号量作为锁
- 数量操作都使用java原子操作类,例:AtomicInteger、AtomicLong等
三、实操
构建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();
}
}
编写同步锁工具类
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同步锁实现的更多相关文章
- Java Learning:并发中的同步锁(synchronized)
引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...
- Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures
参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...
- python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)
昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...
- Java同步锁——lock与synchronized 的区别【转】
在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...
- 单例模式(懒汉、饿汉、同步锁、static、枚举)实现
使用前提: 需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费资源过多 三要素: 1.构造方法私有化: 2.实例化的变量引用私有化: 3.获取实例的方法共有. 1.饿汉式单例 弊端:在类装载的时 ...
- jvm层面锁优化+一般锁的优化策略
偏向锁: 首先了解对象头MARK指针(对象头标记,32位): 存储GC标记,对象年龄,对象Hash,锁信息(锁记录的指针,偏向锁线程的ID) 大部分情况是没有竞争的,所以可以通过偏向来提高性能 所谓的 ...
- Python并发编程-进程 线程 同步锁 线程死锁和递归锁
进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...
- 同步锁Lock
用于解决多线程安全问题有三种方式: 同步代码块(隐式锁,基于JVM) 同步方法(隐式锁,基于JVM) 同步锁(显式锁,jdk1.5后出现,相对于前两种方式,更加灵活) 下面通过一段程序来说明一下同步锁 ...
- Python3 进程 线程 同步锁 线程死锁和递归锁
进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...
- 同步锁之lock
一. synchronized的缺陷 当一个代码块被synchronized修饰时,同时该代码块被一个线程执行,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: ...
随机推荐
- Elasticsearch:运用 shard_size 来提高term aggregation的精度
- vivo 公司 Kubernetes 集群 Ingress 网关实践
文章转载自:https://mp.weixin.qq.com/s/qPqrJ3un1peeWgG9xO2m-Q 背景 vivo 人工智能计算平台小组从 2018 年底开始建设 AI 计算平台至今,已经 ...
- jumpserver 2222端口的使用
可以这样理解: 通过在jumpserver的web界面添加的用户,相应的也有权限通过远程使用命令的方式登陆jumpserver,进行相应的管理,只不过使用的端口是2222端口,不是常见的22端口. 一 ...
- My life of Honker Security Commando
红客突击队 && 红客突击分队 红客突击队,于2019年,由队长k龙联合国内多位顶尖高校研究生牵头成立.其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验.团 ...
- PAT (Basic Level) Practice 1030 完美数列 分数 25
给定一个正整数数列,和正整数 p,设这个数列中的最大值是 M,最小值是 m,如果 M≤mp,则称这个数列是完美数列. 现在给定参数 p 和一些正整数,请你从中选择尽可能多的数构成一个完美数列. 输入格 ...
- Springboot集成阿里云短信
目录 1 前言 2 准备工作 2.1 了解流程 2.2 配置信息 2.3 短信签名和模板 2.3.1 签名 2.3.2 模板 2.3.3 存入数据库 3 SDK 4 集成Springboot 4.1 ...
- 4.ElasticSearch系列之基本概念
1. 文档 ElasticSearch是面向文档的,文档是所有可搜索数据的最小单位 文档会被序列化成JSON格式,保存在ES中 每个文档都有一个unique ID #查看前10条文档,了解文档格式 P ...
- java集合框架复习----(3)Set
文章目录 四.set集合 1.hashSet[重点] 2.TreeSet 四.set集合 无序.无下标.元素不可重复 1.hashSet[重点] == 数组+链表+红黑树== 基于hashcode计算 ...
- 创建第一个springboot项目、用springboot实现页面跳转、@Controller和@RestController的区别
文章目录 一.第一个spring boot项目 二.spring boot跳转到指定页面 三.怎样将后台的信息传递到前台 四. @Controller和@RestController的区别? 一.第一 ...
- python和C语言从路径中获取文件名
1.Python import os file_name = os.path.basename(filepath)#带后缀的文件名(不含路径) file_name_NoExtension = os.p ...