代码地址如下:
http://www.demodashi.com/demo/12520.html

0、准备工作

0-1 运行环境

  1. jdk1.8
  2. gradle
  3. 一个能支持以上两者的代码编辑器,作者使用的是IDEA。

0-2 知识储备

  1. 对Java并发包,互斥锁 有一定的理解。
  2. 对Redis数据库有一定了解。
  3. 本案例偏难,案例内有注释讲解,有不明白的地方,或者不正确的地方,欢迎联系作者本人或留言。

1、设计思路

1.1 项目结构



图2——项目结构

/lock/DestributeLock.java:锁的具体实现类,有lock()和unlock()两个方法。

/lock/DestributeLockRepository.java:锁的工厂类,用来配置Redis连接信息,获取锁的实例。

/lock/Expired**.java:Redis的 pub/sub 功能相关类,用来实现超时自动解锁。

/test.java:3个测试类,用来模拟不同的加锁情况。

1.2 实现难点

咱们可以在网上轻松的找到,用Redis实现简单的互斥锁的案例。

那为什么说是简单的?因为不安全

1.Redis的stNX()与expire()方法是两个独立的操作,即非原子性。咱们可以假设这么一个情况,当你执行stNX()之后,服务器挂了,没有执行expire()方法。那如果没有去解锁的话,是不是就死锁了?所以,咱们需要保证这两个操作的原子性。

2.expire()方法只是告知Redis在一定时间后,自动删除某个键。但是,服务器并不知道expire()在超时之后,是否成功地解锁(删除了key)。所以,咱们需要Redis通知服务器expire()方法已经彻底执行完毕,即Redis已经删除了key,才能确定为解锁状态。

2、具体实现

2.1 DestributeLockRepository.java

public class DistributeLockRepository {

    private String host;
private int port;
private int maxTotal;
private JedisPool jedisPool; /**
* @param host redis地址
* @param port 端口
* @param maxTotal 锁的最大个数,也就是说最多有maxTotal个线程能同时操作锁
*
**/
public DistributeLockRepository(String host,int port,int maxTotal){
this.host = host;
this.port = port;
this.maxTotal = maxTotal; JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPool = new JedisPool(jedisPoolConfig, host, port);
} public DistributeLock instance(String lockname) {
Jedis jedis = jedisPool.getResource();
// 若超过最大连接数,会在这里阻塞
return new DistributeLock(jedis, lockname);
} }

2.2 DestributeLock.java

public class DistributeLock implements ExpiredListener {

    private Jedis redisClient = null;
private String key = ""; //锁的key
private int expire = 0; //Redis自动删除时间
private long startTime = 0L; //尝试获取锁的开始时间
private long lockTime = 0L; //获取到锁的时间
private boolean lock = false; //锁状态 private void setLock(boolean lock) {
this.lock = lock;
} private void closeClient() {
redisClient.close();
} private static String script =
"if redis.call('setnx',KEYS[1],KEYS[2]) == 1 then\n"
+ "redis.call('expire',KEYS[1],KEYS[3]);\n"
+ "return 1;\n"
+ "else\n"
+ "return 0;\n"
+ "end\n"; DistributeLock(Jedis jedis, String key) {
this.redisClient = jedis;
this.key = key;
} @Override
public void onExpired() {
ExpiredManager.remove(key,this);
this.setLock(false);
redisClient.close();//关闭连接
redisClient = null;
System.out.println(key +lockTime+ "Redis超时自动解锁" + Thread.currentThread().getName());
} //redisClient.psubscribe(new ExpiredSub(this),"__key*__:expired"); /**
* @param timeout 锁阻塞超时的时间 单位:毫秒
* @param expire redis锁超时自动删除的时间 单位:秒
* @return true-加锁成功 false-加锁失败
*/ public synchronized boolean lock(long timeout, int expire) {
this.expire = expire;
this.startTime = System.currentTimeMillis();
if (!lock) {
//System.out.println(Thread.currentThread().getName() + lock);
try {
//在timeout的时间范围内不断轮询锁
while (System.currentTimeMillis() - startTime < timeout) {
//System.out.println(Thread.currentThread().getName() + "inWhile");
//使用Lua脚本保证setnx与expire的原子性
Object object = redisClient.eval(script, 3, key, "a", String.valueOf(expire));
//System.out.println(Thread.currentThread().getName() + "afterScript");
if ((long) object == 1) {
this.lockTime = System.currentTimeMillis();
//锁的情况下锁过期后消失,不会造成永久阻塞
this.lock = true;
System.out.println(key+lockTime + "加锁成功" + Thread.currentThread().getName());
//交给超时管理器
ExpiredManager.add(key, this);
return this.lock;
}
System.out.println(key+lockTime +"出现锁等待" + Thread.currentThread().getName());
//短暂休眠,避免可能的活锁
Thread.sleep(500);
}
System.out.println(key+lockTime +"锁超时" + Thread.currentThread().getName());
} catch (Exception e) {
if(e instanceof NullPointerException){
throw new RuntimeException("无法对已经解锁后的锁重新加锁,请重新获取", e);
}
throw new RuntimeException("locking error", e);
}
} else {
//System.out.println(key + "不可重入/用");
throw new RuntimeException(key +lockTime+ "不可重入/用");
}
this.lock = false;
return this.lock; } public synchronized void unlock() { if (this.lock) {
//解决在 Redis自动删除锁后,尝试解锁的问题
if (System.currentTimeMillis() - lockTime <= expire) {
redisClient.del(key);//直接删除 如果没有key,也没关系,不会有异常
}
this.lock = false;
redisClient.close();//关闭连接
redisClient = null;
System.out.println(key+ lockTime+ "解锁成功" + Thread.currentThread().getName());
}else {
System.out.println(key +lockTime+ "已经解锁" + Thread.currentThread().getName());
} } }

2.3 ExpiredManager.java

public class ExpiredManager {

    private static final String HOST = "localhost";

    private static final Integer PORT = 16379;

    private static boolean isStart = false;

    private static Jedis jedis;

    private static ConcurrentHashMap<String,CopyOnWriteArrayList<ExpiredListener>> locks = new ConcurrentHashMap<>();

    public static void add(String key,ExpiredListener listener){
CopyOnWriteArrayList<ExpiredListener> copyOnWriteArrayList = locks.get(key);
if(copyOnWriteArrayList==null){
copyOnWriteArrayList = new CopyOnWriteArrayList<ExpiredListener>();
copyOnWriteArrayList.add(listener);
locks.put(key,copyOnWriteArrayList);
}else {
copyOnWriteArrayList.add(listener);
} } public static void remove(String key,ExpiredListener listener){
CopyOnWriteArrayList<ExpiredListener> copyOnWriteArrayList = locks.get(key);
if(copyOnWriteArrayList!=null){
copyOnWriteArrayList.remove(listener);
}
} public synchronized static void start(){ if(!isStart) {
isStart = true;
jedis = new Jedis(HOST, PORT);
new Thread(new Runnable() {
@Override
public void run() {
try {
jedis.psubscribe(new ExpiredSub(locks), "__key*__:expired");
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}).start(); }
} public synchronized static void close(){
if(isStart) {
isStart = false;
jedis.close();
}
} }

2.4 测试

public class Test3 {
public static void main(String[] args) { //第三个参数表示 同一时间 最多有多少锁能 处于加锁或者阻塞状态 其实就是连接池大小
DistributeLockRepository distributeLockRepository = new DistributeLockRepository("localhost", 16379, 6);
//获取锁实例
DistributeLock lock1 = distributeLockRepository.instance("lock[A]");
DistributeLock lock2 = distributeLockRepository.instance("lock[A]"); //开启超时解锁管理器
ExpiredManager.start(); //lock1和lock2其实模拟的是两个消费者,对同一资源(lock[A])的竞争使用
lock1.lock(1000 * 20L, 5);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.lock(1000 * 20L, 5);
lock1.unlock();
System.out.println("----"); //关闭超时解锁管理器
ExpiredManager.close();
}
}

Test3的运行结果:

3、总结

上面是贴出的主要代码,完整的请下载demo包,有不明白的地方请在下方评论,或者联系邮箱yaoyunxiaoli@163.com。

我是妖云小离,这是我第一次在Demo大师上发文章,感谢阅读。单机Redis实现分布式互斥锁

代码地址如下:
http://www.demodashi.com/demo/12520.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

单机Redis实现分布式互斥锁的更多相关文章

  1. 基于(Redis | Memcache)实现分布式互斥锁

    设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 缓存击穿 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则 ...

  2. 基于Zookeeper实现的分布式互斥锁 - InterProcessMutex

    Curator是ZooKeeper的一个客户端框架,其中封装了分布式互斥锁的实现,最为常用的是InterProcessMutex,本文将对其进行代码剖析 简介 InterProcessMutex基于Z ...

  3. 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存

    原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...

  4. 利用redis实现分布式事务锁,解决高并发环境下库存扣减

    利用redis实现分布式事务锁,解决高并发环境下库存扣减   问题描述: 某电商平台,首发一款新品手机,每人限购2台,预计会有10W的并发,在该情况下,如果扣减库存,保证不会超卖 解决方案一 利用数据 ...

  5. 【redis】基于redis实现分布式并发锁

    基于redis实现分布式并发锁(注解实现) 说明 前提, 应用服务是分布式或多服务, 而这些"多"有共同的"redis"; (2017-12-04) 笑哭, 写 ...

  6. 基于单机redis的分布式锁实现

    最近我们有个服务经常出现存储的数据出现重复,首先上一个系统流程图: 用户通过http请求可以通知任务中心结束掉自己发送的任务,这时候任务中心会通过MQ通知结束服务去结束任务保存数据,由于任务结束数据计 ...

  7. springboot 中单机 redis 实现分布式锁

    在微服务中经常需要使用分布式锁,来执行一些任务.例如定期删除过期数据,在多个服务中只需要一个去执行即可. 以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常 ...

  8. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  9. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

随机推荐

  1. (九)经典_STM32_ADC多通道采样的例子

    STM32 ADC多通道转换描述:用ADC连续采集11路模拟信号,并由DMA传输到内存.ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ.在每次转换结束后,由DMA循环将转换的数据传输到 ...

  2. python搭建区块链

    #!/usr/bin/env python # encoding: utf-8 ''' 我们要创建一个 Blockchain 类 ,他的构造函数创建了一个初始化的空列表(要存储我们的区块链),并且另一 ...

  3. Error:Execution failed for task ':app:transformClassesWithDexForDebug'. > com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.Exec

    Error:Execution failed for task ':app:transformClassesWithDexForDebug'.> com.android.build.api.tr ...

  4. (1) python--numpy

    废话不多说,直接上代码 import numpy as np # 如何创建一个数组 arr = np.array([1, 2, 3, 4]) print(arr) # [1 2 3 4] # 查看数组 ...

  5. Burnside引理与polay定理

    #Burnside引理与polay定理 引入概念 1.置换 简单来说就是最元素进行重排列 是所有元素的异议映射,即\([1,n]\)映射到\([1,n]\) \[ \begin{pmatrix} 1& ...

  6. luogu P1122 最大子树和

    题目描述 小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题.一天他早晨骑车去上课,路上见到一个老伯正在修剪花花草草,顿时想到了一个有关修剪花卉的问题.于是当日课后,小明 ...

  7. POJ 2155 Matrix(树状数组+容斥原理)

    [题目链接] http://poj.org/problem?id=2155 [题目大意] 要求维护两个操作,矩阵翻转和单点查询 [题解] 树状数组可以处理前缀和问题,前缀之间进行容斥即可得到答案. [ ...

  8. POJ 3977:Subset(折半枚举+二分)

    [题目链接] http://poj.org/problem?id=3977 [题目大意] 在n个数(n<36)中选取一些数,使得其和的绝对值最小. [题解] 因为枚举所有数选或者不选,复杂度太高 ...

  9. 来自Jakob Jenkov的Jackson教程

    Jakob Jenkov是Java界的牛人. 下面是收集的与Jackson有关的教程: JSON: http://tutorials.jenkov.com/java-json/index.html J ...

  10. java内存缓存,节省内存

    缓存的对象 这个问题就是我们上面提到的极端情况,在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回 ...