【JUC知识】单一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,然后唤醒等待的线程 

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号