思路就是Redis解决进程间的竞争,Java解决同进程内线程的竞争,这样,可以达到一个减少Redis访问次数的一个效果。
阻塞到锁释放,使用Redis无法实现,所以使用Java的阻塞机制。轮询sleep会造成不必要的资源浪费,所以使用synchronized锁的wait来实现阻塞,再通过Redis的发布订阅来监听到锁删除时,唤醒阻塞线程。
以上就是大题思路,然后上代码
public class RedisLock extends JedisPubSub {
private static final JedisPool JEDIS_POOL;
// 存储重入次数
private static final ThreadLocal<Map<String, Integer>> LOCK_REENTRY_COUNT = new ThreadLocal<>();
// 存储key对应锁
private static final ConcurrentMap<String, Object> LOCK_MAP = new ConcurrentHashMap<>();
static {
GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(1000);
JEDIS_POOL = new JedisPool(poolConfig, "127.0.0.1", 6379);
// 开启一个线程监听锁的key删除或过期
new Thread(() -> {
try (Jedis jedis = JEDIS_POOL.getResource()) {
jedis.psubscribe(new RedisLock(), "__keyevent*:expired", "__keyevent*:del");
}
}).start();
}
private RedisLock() {
}
/**
* 以阻塞的方式锁定一个redis的key,直到拿到锁为止
*
* @param redisLockKey 要锁定的key
* @param expire
* @throws InterruptedException
*/
public static void lock(String redisLockKey, int expire) throws InterruptedException {
// 记录重入次数的map
Map<String, Integer> lockReentryCountMap = LOCK_REENTRY_COUNT.get();
if (lockReentryCountMap == null) {
// 不存在则创建一个
LOCK_REENTRY_COUNT.set(lockReentryCountMap = new HashMap<>());
}
// 取得重入次数
int reentryCount = lockReentryCountMap.getOrDefault(redisLockKey, 0);
if (reentryCount > 0) {
// 不是第一次获取锁,只对次数+1
lockReentryCountMap.put(redisLockKey, reentryCount + 1);
return;
}
while (true) {
// 先在内存中竞争
Object newLock = new Object();
Object lock = LOCK_MAP.putIfAbsent(redisLockKey, newLock);
if (lock != null) {
// 如果放入的锁不是此线程创建的,则说明有其他线程也在抢锁,让给其他线程
synchronized (lock) {
// 阻塞前检测一下锁有没有被释放
if (LOCK_MAP.get(redisLockKey) == lock) {
// 等待锁释放
lock.wait(expire);
}
}
continue;
}
// 走到这,说明通过了内存竞争
lock = newLock;
synchronized (lock) {
try (Jedis jedis = JEDIS_POOL.getResource()) {
// 然后进行redis竞争
if (jedis.set(redisLockKey, "", SetParams.setParams().nx().ex(expire)) != null) {
// redis竞争通过
lockReentryCountMap.put(redisLockKey, 1);
return;
}
// 等待锁释放
lock.wait(expire);
}
}
}
}
/**
* 解锁
*
* @param redisLockKey 要解锁的key
*/
public static void unlock(String redisLockKey) {
Map<String, Integer> lockReentryCountMap = LOCK_REENTRY_COUNT.get();
if (lockReentryCountMap == null) {
// 记录重入次数的map不存在,则没什么可释放的
return;
}
int reentryCount = lockReentryCountMap.getOrDefault(redisLockKey, 0);
if (reentryCount <= 0) {
// 没有拿到过锁,也没什么可释放的
return;
}
if (--reentryCount > 0) {
// 重入次数减少后,依然大于0,则更新
lockReentryCountMap.put(redisLockKey, reentryCount);
return;
}
// 重入次数减少后为0,则释放
try (Jedis jedis = JEDIS_POOL.getResource()) {
// 删除计数
lockReentryCountMap.remove(redisLockKey);
// 释放redis锁,监听删除线程会通知抢锁线程重新竞争
jedis.del(redisLockKey);
}
}
@Override
public void onPMessage(String pattern, String channel, String message) {
// 获取并删除对应锁
Object lock = LOCK_MAP.remove(message);
if (lock == null) {
return;
}
// 通知解锁
synchronized (lock) {
lock.notifyAll();
}
}
}
大家如果觉得我实现的方式有什么问题,欢迎随时指出