每天忙碌的日子,也不要忘记了好好生活 🏠

vuePress-theme-reco happylay 🐑    2020 - 2021
每天忙碌的日子,也不要忘记了好好生活 🏠 每天忙碌的日子,也不要忘记了好好生活 🏠

Choose mode

  • dark
  • auto
  • light
主页
分类
  • 手册
  • 前端
  • 后端
  • 工作
  • 相册
  • 文档
标签
时间轴
文档
  • 轨迹
个人空间
  • 哔哩哔哩
  • 编辑博客
工具集
  • 后端工具

    • 在线json解析
    • yml格式转换
    • websocket测试
    • 时间戳转换
    • cron表达式
    • linux程序包
    • 大小写转换
    • toml格式转换
  • 后端框架

    • Spring
    • GoFrame
  • 前端工具

    • Vant移动端组件库
    • Element桌面端组件库
    • uni-app移动端框架
    • uview移动端框架
    • colorui2.0文档
    • Figma
    • Codepen
    • Dribbble
    • Iconfont阿里矢量图库
    • IconPark图标库
    • Icomoon
    • Remixicon
    • favicon图标制作
  • 开发环境

    • windows包管理器-baulk
    • windows包管理器-scoop
    • windows原版镜像
    • nexus3仓库
  • 微服务

    • 版本兼容关系
    • k8s在线配置
    • k8s接口文档
GitHub
author-avatar

happylay 🐑

34

文章

24

标签

主页
分类
  • 手册
  • 前端
  • 后端
  • 工作
  • 相册
  • 文档
标签
时间轴
文档
  • 轨迹
个人空间
  • 哔哩哔哩
  • 编辑博客
工具集
  • 后端工具

    • 在线json解析
    • yml格式转换
    • websocket测试
    • 时间戳转换
    • cron表达式
    • linux程序包
    • 大小写转换
    • toml格式转换
  • 后端框架

    • Spring
    • GoFrame
  • 前端工具

    • Vant移动端组件库
    • Element桌面端组件库
    • uni-app移动端框架
    • uview移动端框架
    • colorui2.0文档
    • Figma
    • Codepen
    • Dribbble
    • Iconfont阿里矢量图库
    • IconPark图标库
    • Icomoon
    • Remixicon
    • favicon图标制作
  • 开发环境

    • windows包管理器-baulk
    • windows包管理器-scoop
    • windows原版镜像
    • nexus3仓库
  • 微服务

    • 版本兼容关系
    • k8s在线配置
    • k8s接口文档
GitHub
  • 2021

    • casbin实现权限控制
    • dig实现依赖注入
    • ffmpeg视频推流总结
    • gcc编译环境配置
    • goframe常见问题总结
    • goframe框架总结
    • golang工具总结
    • golang语法总结
    • protobuf安装与使用
    • redis实现分布式锁
    • 常用golang库总结

redis实现分布式锁

vuePress-theme-reco happylay 🐑    2020 - 2021

redis实现分布式锁

happylay 🐑 2021-02-01 12:11:48 java

摘要: redis实现分布式锁 时间: 2021-02-01


# redis实现分布式锁

package com.happylay.config.redis.lock;

import com.happylay.config.redis.write.RedisStringReadWriteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁
 *
 * @Author: happylay
 * @Date: Created in 2020-06-18 13:22
 */
@Component
@Slf4j
public class RedisLock {

    @Autowired
    private RedisStringReadWriteService redisStringReadWriteService;

    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    /**
     * 加锁
     *
     * @param key     加锁业务
     * @param timeOut 超时时间
     * @param unit    时间单位 (TimeUnit.SECONDS)
     * @return
     */
    public Boolean lock(String key, Long timeOut, TimeUnit unit) {

        // 加个判断在同一个线程中threadLock中有值得话,就是表明该该线程已经获取过锁了,我们直接返回true ,保证锁的可重入性
        if (threadLocal.get() == null) {
            // 新启动一个线程,每隔10s给锁加过期时间,防止业务时间超过过期时间
            Thread addThread = new Thread(() -> {

                // TODO 获取锁的超时时间,超过这个时间则放弃获取锁(默认超时时间30分钟,防止线程未被释放)
                long end = System.currentTimeMillis() + 1000 * 60 * 30;

                while (true && System.currentTimeMillis() < end) {
                    try {
                        // 每隔十秒延迟锁时长
                        Thread.sleep(10000);
                        // 延长锁时长
                        redisStringReadWriteService.expire(key, timeOut, unit);
                    } catch (Exception e) {
                        log.warn("RedisLock释放分布式锁,同时主动中断线程。{}", e.getMessage());
                    }
                }
            });

            // 启动线程
            addThread.start();
            // 保证不同线程锁的唯一性
            String uuid = addThread.getId() + ":" + UUID.randomUUID().toString();
            // 设置当前线程唯一编号
            threadLocal.set(uuid);
            // 在redis中插入一条key超时时间为30s把添加过期时间的线程id也作为key,后面通过id在释放锁的时候停止该线程
            Boolean isLock = redisStringReadWriteService.setIfAbsent(key, uuid, timeOut, unit);

            // TODO 尝试获取锁超时时间(默认1分钟)
            long endTime = System.currentTimeMillis() + 1000 * 60 * 1;

            // 支持阻塞性获取锁失败重复去获取锁
            while (!isLock) {

                // 尝试重新获取锁
                isLock = redisStringReadWriteService.setIfAbsent(key, uuid, timeOut, unit);

                // TODO 如果1分钟之内未获取到锁,直接返回
                if (!isLock && System.currentTimeMillis() < endTime) {

                    // TODO 终止当前未获取锁线程(延长持有锁时间线程)
                    stopThread();

                    return false;
                }

            }

            return isLock;

        } else {
            return true;
        }

    }

    /**
     * 解锁
     *
     * @param key 业务主键
     * @return
     */
    public void release(String key) {

        // 加一个线程id判断,防止其他线程业务锁释放掉其他业务的锁
        if (threadLocal.get().equals(redisStringReadWriteService.get(key))) {
            // 获取延期的线程id停止该线程
            String s = redisStringReadWriteService.get(key);
            String[] split = s.split(":");

            // 查找当前线程
            Thread thread = findThread(Long.parseLong(split[0]));
            if (null != thread) {
                thread.interrupt();
            }
            // 释放锁
            redisStringReadWriteService.delete(key);
            // 释放线程id
            threadLocal.remove(); // 防止线程复用,释放锁
        }
    }

    /**
     * 通过线程id获得线程
     *
     * @param threadId 线程id
     * @return
     */
    private Thread findThread(long threadId) {
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        while (group != null) {
            Thread[] threads = new Thread[(int) (group.activeCount() * 1.2)];
            int count = group.enumerate(threads, true);
            for (int i = 0; i < count; i++) {
                if (threadId == threads[i].getId()) {
                    return threads[i];
                }
            }
            group = group.getParent();
        }
        return null;
    }

    /**
     * TODO 终止当前未获取锁线程(延长持有锁时间线程)
     */
    private void stopThread() {
        // 获取当前线程自定义唯一uuid
        String dieUuid = threadLocal.get();
        if (!StringUtils.isEmpty(dieUuid)) {
            String[] split = dieUuid.split(":");
            // 查找当前线程
            Thread thread = findThread(Long.parseLong(split[0]));
            // 停止当前线程
            if (null != thread) {
                thread.interrupt();
            }
            // 释放线程id
            threadLocal.remove();
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
编辑文档
最后一次更新: 2021/2/2 下午12:34:17
欢迎来到小屋。
看板娘