使用redis的setnx可以非同一线程进行加锁和解锁(附源码)

  • 问题背景
  • 项目搭建
  • 总结
  • Lyric: 那在终点之前

问题背景

Redisson做分布式锁是目前比较流行的方式,但是在使用的过程中遇到一些坑:

  • Redisson的分布式锁只能通过创建锁的线程进行解锁,正所谓解铃还须系铃人,不是同一个线程解锁会报异常
  • 因为Redisson是为锁而生,所以一开始设计的时候,为了防止死锁,默认锁的过期时间为30S
  • 当时我居然傻到用单元测试来测试Redisson的分布式锁,我太傻了,单元测试之后马上就会结束项目运行,那么就没有线程持有锁了,更别说还需要同线程解锁了

但我的项目中做了分布式任务调度,定时去扫描任务完成没有,完成了再进行解锁,但定时的扫描线程不是之前的加锁线程了,所以使用redisson做分布式锁不太合适,本篇介绍redis的分布式锁的模板使用

注意事项:

  • 代码是从我的这篇文章进行添加修改的
  • 可以自己创建工程,也可以下载源码进行参考
  • 默认已安装redis,可以使用安装包安装看这篇文章,使用docker安装看这篇文章

项目搭建

1 在RedisUtils工具类中添加setNX方法,value值可以设置为globalId,这样具有唯一性,每次解锁还有增加一层value的判断保证更安全

package com.yg.redisson.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** @Author suolong* @Date 2022/4/26 13:33* @Version 2.0*/@Service("redisUtils")
@Slf4j
public class RedisUtils {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 普通缓存获取* @param key 键* @return 值*/public Object get(String key) {try {return key == null ? null : redisTemplate.opsForValue().get(key);}catch (Exception ex){log.error("获取缓存异常", ex);return null;}}public boolean del(String key){try {return Boolean.TRUE.equals(redisTemplate.delete(key));}catch (Exception e){log.error("删除缓存键异常",e);return false;}}public Long increment(String key, Long delta){try {return redisTemplate.opsForValue().increment(key, delta);}catch (Exception ex){log.error("操作缓存异常",ex);return null;}}public void saveByPipeLine(Map<String, Map<String, String>> dataMap){try {final RedisSerializer keySerializer = redisTemplate.getKeySerializer();final RedisSerializer valueSerializer = redisTemplate.getValueSerializer();redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {dataMap.forEach((key,value) -> redisConnection.set(Objects.requireNonNull(keySerializer.serialize(key)), Objects.requireNonNull(valueSerializer.serialize(value)), Expiration.seconds(6 * 3600), RedisStringCommands.SetOption.UPSERT));return null;});}catch (Exception ex){log.error("pipeline存储异常",ex);}}public void saveByPipeLineMap(Map<String, String> dataMap, String key){try {final RedisSerializer keySerializer = redisTemplate.getKeySerializer();final RedisSerializer valueSerializer = redisTemplate.getValueSerializer();redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {redisConnection.set(Objects.requireNonNull(keySerializer.serialize(key)), Objects.requireNonNull(valueSerializer.serialize(dataMap)), Expiration.seconds(6 * 3600), RedisStringCommands.SetOption.UPSERT);return null;});}catch (Exception ex){log.error("pipeline存储异常",ex);}}public List<Object> getByPipeLine(List<String> keys){try {return redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {for (String key : keys) {redisConnection.get(key.getBytes(StandardCharsets.UTF_8));}return null;});}catch (Exception ex){log.error("pipeline读取异常",ex);}return Collections.emptyList();}/*** 普通缓存放入* @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间* @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();log.error("设置缓存异常:{}",e.getLocalizedMessage());return false;}}public Long del(Set<String> key) {try {if (CollectionUtils.isNotEmpty(key)) {return redisTemplate.delete(key);}return null;}catch (Exception ex){log.error("删除缓存键异常:{}", ex.getLocalizedMessage());return null;}}//设置锁名,value值,过期时间public boolean setNX(String lockKey, String value, long expiryTime) {if (redisTemplate == null) {log.info("redisTemplate is null");return false;}try {Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, value, expiryTime, TimeUnit.SECONDS);assert flag != null;if (!flag) {log.info("加锁失败");return false;}log.info("Thread [{}] redisTemplate lock [{}] success", Thread.currentThread().getName(), lockKey);// 加锁成功return true;} catch (Exception e) {log.info("加锁失败", e);log.error("redisTemplate lock [{}] Exception:", lockKey, e);return false;}}
}

2 setNX测试,先设置setNX,然后进行del,此时两个方法并不是同一个线程

    @Testvoid redisDel() {boolean res = redisUtils.del("yuan");log.info("res: {}", res);}@Testvoid redisSetNX() {boolean res = redisUtils.setNX("yuan", "123",12*60*60L);log.info("res: {}", res);}

总结

  • 需要不同线程删除锁,使用这种方式比较合适

作为程序员第 125 篇文章,每次写一句歌词记录一下,看看人生有几首歌的时间,wahahaha …

Lyric: 那在终点之前

使用redis的setnx可以非同一线程进行加锁和解锁(附源码)相关推荐

  1. HashMap为什么线程不安全?(附源码)

    文章原文地址点击查看原文 首先HashMap是线程不安全的,其主要体现:1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失.2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况. ...

  2. php(TP5)+redis实现秒杀抢购(限制用户购买次数)(附源码)

    原文地址:https://blog.csdn.net/hzbskak/article/details/103718369 首先看我改了之后的主要代码: <?php namespace app\i ...

  3. 单机redis工具类的使用附源码

    单机redis工具类的使用附源码 问题背景 项目搭建 代码测试 总结 Lyric: 怎么隐藏我的悲伤 问题背景 redis常用的工具类 注意事项: 默认已安装redis,可以使用安装包安装看这篇文章, ...

  4. flink sql 知其所以然(二)| 自定义 redis 数据维表(附源码)

    感谢您的关注  +  点赞 + 再看,对博主的肯定,会督促博主持续的输出更多的优质实战内容!!! 1.序篇-本文结构 背景篇-为啥需要 redis 维表 目标篇-做 redis 维表的预期效果是什么 ...

  5. 详解非局部均值滤波原理以及用MATLAB源码实现

    详解非局部均值滤波原理以及用MATLAB源码实现 序言 均值滤波.中值滤波.高斯滤波在滤除噪声的过程中,无可避免的使图像的边缘细节和纹理信息所被滤除.针对此问题,Buades[1]等人提出了非局部均值 ...

  6. 【Matlab图像去噪】改进非局部均值红外图像混合噪声【含源码 1640期】

    一.代码运行视频(哔哩哔哩) [Matlab图像去噪]改进非局部均值红外图像混合噪声[含源码 1640期] 二.matlab版本及参考文献 1 matlab版本 2014a 2 参考文献 [1] 李方 ...

  7. Redis在Java中的使用及连接数据库(附源码)

    Redis在Java中的使用及连接数据库(附源码) 引言: 本文主要分享了Redis如何在IDEA中部署,运行:模拟加入Redis的操作: 文章目录 Redis在Java中的使用及连接数据库(附源码) ...

  8. 分布式架构-Redis 从入门到精通 完整案例 附源码

    导读 篇幅较长,干货十足,阅读需要花点时间,全部手打出来的字,难免出现错别字,敬请谅解.珍惜原创,转载请注明出处,谢谢~! NoSql介绍与Redis介绍 什么是Redis? Redis是用C语言开发 ...

  9. 来,一起手撸一个简版 Redis(附源码)

    点击上方 视学算法,选择 设为星标 优质文章,及时送达 作者 | 凯京技术团队 来自 | my.oschina.net/keking/blog/3037372 今天主要介绍两个开源项目,然后创建应用最 ...

最新文章

  1. linux下用js生成xml,js2xml:将javascript字符串转换为xml
  2. php在线读取pdf文件大小_南公子私藏PDF神器曝光
  3. python2.7 threading RLock/Condition文档翻译 (RLock/Condition详解)
  4. android write file,Android Study Day 3 --Android File Read And Write
  5. Chrome之控制台使用【转载】
  6. sql字符处理函数concat()、concat_ws()
  7. apache+gzip+ssl网页压缩率50%
  8. PTV-VISSIM交通仿真
  9. gmssl java_GMSSL编译运行Java Wrapper踩坑记录
  10. 二路归并排序和多路归并排序
  11. 打通C到B,“能者多劳”的小冰
  12. 三次bezier曲线 MATLAB,Matlab 画二次及三次Bezier曲线,8控制点的B样条曲线
  13. 执行SOA——SOA实践指南
  14. C3H5 3d立体魔方效果
  15. 华为手机桌面角标开发
  16. CCNET的参考文件
  17. 统一网关Geteway
  18. 计算机中英文术语对照表
  19. PMBOK泛读(第十一章) - 项目风险管理
  20. 兔年行兔礼,回帖都有奖【炮哥】赶紧去提名

热门文章

  1. 什么东西能帮助睡眠?曾经五年睡不好觉的朋友在用几个东西
  2. PL-VIO学习+注释
  3. vim 配置文件 ,高亮+自动缩进+行号+折叠+优化
  4. 值得收藏的12款小众冷门但功能强大的在线神器
  5. 【PAT】A1148 Werewolf - Simple Version
  6. Mininet系列实验(一):Mininet使用源码安装
  7. GB/T28181-2022图像抓拍规范解读及技术实现
  8. 90%的年轻人猝死: 这么玩手机真的会死人的
  9. Tableau可视化项目
  10. 2021-07-02-MySQL必知必会-笔记