1. 开局

在多线程环境中,经常会碰到需要加锁的情况,由于现在的系统基本都是集群分布式部署,JVM的lock已经不能满足分布式要求,分布式锁就这样产生了。。。

百度一下,网上有很多分布式锁的方案或者例子,琳琅满目,看了之后不知所措,总体来说有以下几种:

基于数据库

基于zookeeper

基于redis

基于memcached

各有优缺点和实现难度,这里就不一一分析。本文主要是基于redis的setnx实现分布式锁,比较简单有一定的局限性,欢迎大家提出意见建议!

2. 加锁过程

执行redis的setnx,只有key不存在才能set成功(实际使用的是set(key, value, "NX", "EX", seconds),redis较新版本支持)

如果set成功(同时也设置了key的过期时间),则表示加锁成功

如果set失败,则每次sleep(x)毫秒后不断尝试,直到成功或者超时

3. 释放过程

判断加锁是否成功

如果成功,则执行redis的del删除

4. 问题思考

加锁时,锁的redis key过期时间多长合适?

需要根据业务执行的时间长度来评估,默认30秒满足绝大部分需求,支持动态修改

加锁时,重试超时时间多长合适?本文设置的是过期时间的1.2倍,目的是在最坏的情况下等待锁过期后,尽量保证获取到锁,否则抛出超时异常。这个设置不完全合理

加锁时,重试的sleep时间多长合适?本文采用的是随机[50-300)毫秒,避免出现大量线程同时竞争,目的是错峰吧

释放时,如何避免释放了其他线程的锁(A获取锁后由于挂起导致锁到期自动释放;此时B获取到锁,而A又恢复运行释放了B的锁)?在初始化锁时生个一个唯一字符串,作为redis锁的value;value一致时表明是自己的锁,可以释放

5. 上代码!

用法

RedisLock lock = new RedisLock(redisHelper, lockKey);

try {

// 执行加锁,防止并发问题

lock.tryLock();

// do somethings

doSomethings()

}

finally {

// 释放锁

lock.release();

}

RedisLock实现(注:依赖RedisHepler类,仅仅是对jedis的一层封装,可自行实现)

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* RedisLock

*

* @version 2017-9-21上午11:56:27

* @author xiaoyun.zeng

*/

public class RedisLock {

private Logger logger = LoggerFactory.getLogger(getClass());

/**

* key前缀

*/

private static final String PREFIX = "lock:";

/**

* 操作redis的工具类

*/

private RedisHelper redisHelper;

/**

* redis key

*/

private String redisKey = null;

/**

* redis value

*/

private String redisValue = null;

/**

* 锁的过期时间(秒),默认30秒,防止线程获取锁后挂掉无法释放锁

*/

private int lockExpire = 30;

/**

* 尝试加锁超时时间(毫秒),默认为expire的1.2倍

*/

private int tryTimeout = lockExpire * 1200;

/**

* 尝试加锁次数计数器

*/

private long tryCounter = 0;

/**

* 加锁成功标记

*/

private boolean success = false;

private long startMillis = 0;

private long expendMillis = 0;

/**

* RedisLock

*

* @param redisHelper

* @param lockKey

*/

public RedisLock(RedisHelper redisHelper, String lockKey) {

this.redisHelper = redisHelper;

this.redisKey = PREFIX + lockKey;

// 生成redis value,用于释放锁时比对是否属于自己的锁

// 生成规则 lockKey+时间戳+随机数,避免重复

// 乐观地认为:

// 1、同一毫秒内,随机数相同的概率极小

// 2、释放非自己线程锁的几率极小(release方法有说明这种情况)

this.redisValue = lockKey + "-" + System.currentTimeMillis() + "-" + this.random(10000);

}

/**

* RedisLock

*

* @param redisHelper

* @param lockKey

* @param expire

*/

public RedisLock(RedisHelper redisHelper, String lockKey, int lockExpire) {

this(redisHelper, lockKey);

// 过期时间

this.lockExpire = lockExpire;

// 超时时间(毫秒),默认为expire的1.2倍

this.tryTimeout = lockExpire * 1200;

}

/**

* 尝试加锁

*

* 尝试加锁的过程将会一直阻塞下去,直到加锁成功或超时

*

* @version 2017-9-21下午12:00:07

* @author xiaoyun.zeng

* @return

*/

public void tryLock() throws RuntimeException {

startMillis = System.currentTimeMillis();

// 首次直接请求加锁

if (!lock()) {

do {

// 超时判断,避免永远获取不到锁的情况下,一直尝试

// 超时抛出runtime异常

if (System.currentTimeMillis() - startMillis >= tryTimeout) {

throw new RuntimeException("尝试加锁超时" + tryTimeout + "ms");

}

// 随机休眠[50-300)毫秒

// 避免出现大量线程同时竞争

try {

Thread.sleep(this.random(250) + 50);

}

catch (InterruptedException e) {

// 出现异常直接抛出

throw new RuntimeException(e);

}

}

while (!lock());

}

}

/**

* 释放锁

*

* @version 2017-9-21下午12:00:21

* @author xiaoyun.zeng

* @param lockKey

*/

public void release() {

// 加锁成功才执行释放

if (success) {

// 释放前,检查redis value是否一致

// 避免A获取锁后由于挂起导致锁到期自动释放

// 此时B获取到锁,而A又恢复运行释放了B的锁

String value = redisHelper.get(redisKey);

if (redisValue.equals(value)) {

redisHelper.del(redisKey);

logger.debug("已释放锁:{}", redisValue);

}

}

}

/**

* 加锁

*

* @version 2017-9-21下午6:25:58

* @author xiaoyun.zeng

* @param key

* @param value

* @param lockExpire

* @return

*/

private boolean lock() {

// 加锁计数器+1

tryCounter++;

// 调用redis setnx完成加锁,返回true表示加锁成功,否则失败

success = redisHelper.setNx(redisKey, redisValue, lockExpire);

// 计算总耗时

expendMillis = System.currentTimeMillis() - startMillis;

// 记录日志

if (success) {

logger.debug("加锁成功:尝试{}次,耗时{}ms,{}", tryCounter, expendMillis, redisValue);

}

return success;

}

/**

* 产生随机数

*

* @version 2017-9-22上午10:05:52

* @author xiaoyun.zeng

* @param max

* @return

*/

private int random(int max) {

return (int) (Math.random() * max);

}

}

6. 测试代码

单元测试:

@RunWith(SpringRunner.class)

@SpringBootTest

public class RedisLockTest {

@Autowired

private RedisHelper redisHelper;

@Test

public void test() {

for (int i = 0; i < 10; i++) {

new Thread(new Runnable() {

@Override

public void run() {

RedisLock lock = new RedisLock(redisHelper, "zxy");

try {

lock.tryLock();

try {

Thread.sleep(2 * 1000);

}

catch (InterruptedException e) {

e.printStackTrace();

}

}

finally {

lock.release();

}

}

}).start();

}

while(true) {

}

}

}

日志输出:

2017/10/12 17:47:28.335 [Thread-8] DEBUG [RedisLock.161] 加锁成功:尝试1次,耗时4ms,zxy-1507801648330-6665

2017/10/12 17:47:30.340 [Thread-8] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648330-6665

2017/10/12 17:47:30.351 [Thread-14] DEBUG [RedisLock.161] 加锁成功:尝试12次,耗时2018ms,zxy-1507801648333-6866

2017/10/12 17:47:32.356 [Thread-14] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648333-6866

2017/10/12 17:47:32.396 [Thread-11] DEBUG [RedisLock.161] 加锁成功:尝试22次,耗时4065ms,zxy-1507801648331-5217

2017/10/12 17:47:34.400 [Thread-11] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648331-5217

2017/10/12 17:47:34.430 [Thread-12] DEBUG [RedisLock.161] 加锁成功:尝试39次,耗时6098ms,zxy-1507801648332-7708

2017/10/12 17:47:36.433 [Thread-12] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648332-7708

2017/10/12 17:47:36.453 [Thread-17] DEBUG [RedisLock.161] 加锁成功:尝试50次,耗时8119ms,zxy-1507801648334-2362

2017/10/12 17:47:38.457 [Thread-17] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648334-2362

2017/10/12 17:47:38.494 [Thread-9] DEBUG [RedisLock.161] 加锁成功:尝试57次,耗时10164ms,zxy-1507801648330-7086

2017/10/12 17:47:40.497 [Thread-9] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648330-7086

2017/10/12 17:47:40.587 [Thread-13] DEBUG [RedisLock.161] 加锁成功:尝试70次,耗时12254ms,zxy-1507801648333-8881

2017/10/12 17:47:42.590 [Thread-13] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648333-8881

2017/10/12 17:47:42.611 [Thread-15] DEBUG [RedisLock.161] 加锁成功:尝试82次,耗时14276ms,zxy-1507801648335-2509

2017/10/12 17:47:44.614 [Thread-15] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648335-2509

2017/10/12 17:47:44.699 [Thread-16] DEBUG [RedisLock.161] 加锁成功:尝试89次,耗时16365ms,zxy-1507801648334-5791

2017/10/12 17:47:46.702 [Thread-16] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648334-5791

2017/10/12 17:47:46.802 [Thread-10] DEBUG [RedisLock.161] 加锁成功:尝试106次,耗时18471ms,zxy-1507801648331-7347

2017/10/12 17:47:48.805 [Thread-10] DEBUG [RedisLock.137] 已释放锁:zxy-1507801648331-7347

java分布式锁工具类_java 通过redis实现分布式锁相关推荐

  1. java时间日期工具类_java日期处理工具类

    java日期处理工具类 import java.text.DecimalFormat; import java.text.ParsePosition; import java.text.SimpleD ...

  2. java 日期处理工具类_Java日期处理工具类DateUtils详解

    本文实例为大家分享了Java日期处理工具类DateUtils的具体代码,供大家参考,具体内容如下 import java.sql.Timestamp; import java.text.ParseEx ...

  3. java 图片合成 工具类_Java实现的图片上传工具类完整实例

    本文实例讲述了Java实现的图片上传工具类.分享给大家供大家参考,具体如下: package com.gcloud.common; import javax.imageio.ImageIO; impo ...

  4. java生成验证码工具类_Java生成图形验证码工具类

    生成验证码效果 validatecode.java 验证码生成类 package cn.dsna.util.images; import java.awt.color; import java.awt ...

  5. java 代理ip工具类_Java基础之java处理ip的工具类

    java处理ip的工具类,包括把long类型的Ip转为一般Ip类型.把xx.xx.xx.xx类型的转为long类型.根据掩码位获取掩码.根据 ip/掩码位 计算IP段的起始IP.根据 ip/掩码位 计 ...

  6. java编写静态工具类_Java编程中静态内部类与同步类的写法示例

    java静态内部类 将某个内部类定义为静态类,跟将其他类定义为静态类的方法基本相同,引用规则也基本一致.不过其细节方面仍然有很大的不同.具体来说,主要有如下几个地方要引起各位程序开发人员的注意. (一 ...

  7. java 连接mysql工具类_java连接Mysql数据库的工具类

    一个封装好的链接Mysql数据库的工具类,可以方便的获取Connection对象关闭Statement.ResultSet.Statment对象等等 复制代码 代码如下: package myUtil ...

  8. java list排序工具类_java 之 Collections集合工具类排序

    数组有工具类Arrays,集合也有一个工具类Collections. sort方法: sort(List list):根据其元素natural ordering对制定的列表进行排序 sort(List ...

  9. java 图片合成 工具类_Java实现的微信图片处理工具类【裁剪,合并,等比例缩放等】...

    本文实例讲述了Java实现的微信图片处理工具类.分享给大家供大家参考,具体如下: 现在 外面核心,图片文章比较少,看了拷贝代码,而用不了,用相应jar包处理,很多等比例缩放,达不到 想要的给予的期望: ...

最新文章

  1. 前端每日实战:143# 视频演示如何用 CSS 的 Grid 布局创作一枚小松鼠邮票
  2. 《LeetCode力扣练习》第11题 C语言版 (做出来就行,别问我效率。。。。)
  3. 如何在Win7以上环境使用VC++6
  4. Android --- RecyclerViwe中使用SnapHelper报错:“An instance of OnFlingListener already set.”
  5. 任务调度框架Quartz基本介绍
  6. 邹建的实现分页的通用存储过程
  7. javascript自制函数图像生成器
  8. 谷歌开源了量子算法框架CIRQ,拥抱NISQ新时代
  9. Educational Codeforces Round 61
  10. (网络编程)SOCKET应用实例
  11. 电脑硬盘怎么测试软件,如何通过软件检测电脑硬盘坏道?
  12. 风险模型 - 变量筛选
  13. Oracle19c 出现 ora-12514
  14. java的setbounds_Swing-setBounds()用法-入门
  15. html5 今日头条视频播放,今日头条app设置自动播放视频的方法
  16. [微服务]API 路由管理--Gateway网关
  17. 减法器(差分放大器)
  18. 如何借助分布式存储 JuiceFS 加速 AI 模型训练
  19. TOM、腾讯、网易|你了解大厂企业邮箱的登陆入口吗?
  20. 综合案例之学成在线首页

热门文章

  1. 车间生产能耗管控方案_如何给生产车间降温 环保空调的这些方案一定能帮到你...
  2. 698A. Vacations
  3. 手把手教你从0创建STM32串口空闲+DMA数据接收工程
  4. @Autowired和构造方法执行的顺序解析
  5. java 代码重用需要注意的事项_程序员笔记|编写高性能的Java代码需要注意的4个问题...
  6. uinty粒子系统子物体变大_Unity2018粒子系统全息讲解,坑深慎入(3)
  7. OSG+VS2013+Win7 环境搭建,osgvs2013
  8. java的未检查异常有哪些_Java:检查异常与未检查异常
  9. 苹果X可以升级5G吗_苹果x可以用5g网络吗
  10. fortran转换 matlab代码,将Fortran77代码转换为Matlab代码以查找特征值/向量