代码地址如下:
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. (二十七)Linux的inode的理解

    一.inode是什么? 理解inode,要从文件储存说起. 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector).每个扇区储存512字节(相当于0.5KB). 操作系统 ...

  2. Fiddler抓包11-HTTPS证书Actions无法导出问题【转载】

    本篇转自博客:上海-悠悠 原文地址:http://www.cnblogs.com/yoyoketang/tag/fiddler/ 前言 在点Actions时候出现Export Failed:The r ...

  3. 如何在 GitHub 建立个人主页和项目演示页面

    Git.GitHub.TortoiseGit ?http://www.cnblogs.com/guyoung/archive/2012/02/18/8030-.html GitHub Github官网 ...

  4. Guava源码学习(四)新集合类型

    基于版本:Guava 22.0 Wiki:New collection types 0. 简介 Guava提供了很多好用的集合工具,比如Multiset和BiMap,本文介绍了这些新集合类型的使用方式 ...

  5. Ubunntu kylin下安装VmWare Tools(简洁方法)

    1.在VM菜单栏单击虚拟机,选择安装Vmware tools(或者是重装Vmware Tools) 2.会弹出一个界面,就是光盘加载的那个界面,里面有个.******.gz文件 3.复制到桌面(你喜欢 ...

  6. Oracle高级函数

    http://www.cnblogs.com/chen1388/archive/2010/07/06/1771919.html decode函数: decode(aa, 1, 'xs', 2, 'ps ...

  7. Ubuntu 16.04服务器版查看DHCP自动分配的IP、网关、DNS

    说明: 1.在服务器版本中,没有想桌面版一样的NetworkManager工具,所以的一切都是在命令行上操作的. 2.本文只针对DHCP默认分配的IP进行查看. 方法: 1.如果要使用DHCP,那么需 ...

  8. Understanding Memory Technology Devices in Embedded Linux

    转: NAND Chip Drivers NAND technology users such as USB pen drives, DOMs, Compact Flash memory, and S ...

  9. springMVC初探视图解析器——ResourceBundleViewResolver

    视图解析器ResourceBundleViewResolver是根据proterties文件来找对应的视图来解析”逻辑视图“的, 该properties文件默认是放在classpath路径下的view ...

  10. IMAP IDLE模式(推送邮件)

    在电子邮件技术中,IDLE是RFC 2177中描述的一项IMAP功能,它允许客户端向服务器表明它已准备好接受实时通知. Internet消息访问协议IMAP4协议,它要求客户端轮询服务器来更改所选中的 ...