001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.support.redis; 017 018import io.jboot.Jboot; 019import io.jboot.utils.QuietlyUtil; 020 021/** 022 * Created by michael. 023 * <p> 024 * Redis 分布式锁 025 * <p> 026 * 使用方法: 027 * <p> 028 * JbootRedisLock lock = new JbootRedisLock("lockName"); 029 * try{ 030 * boolean acquire = lock.acquire(); 031 * if(acquire){ 032 * // do your something 033 * } 034 * }finally { 035 * lock.release(); 036 * } 037 * <p> 038 * 使用方法2: 039 * JbootRedisLock lock = new JbootRedisLock("lockName"); 040 * lock.runIfAcquired(new Runnable(){ 041 * <p> 042 * public void run() { 043 * //do your something 044 * } 045 * }); 046 */ 047public class JbootRedisLock { 048 049 // 60 秒 expireMsecs 锁持有超时,防止线程在入锁以后,无限的执行下去,让锁无法释放 050 long expireMsecs = 1000 * 60; 051 052 // 锁等待超时 053 long timeoutMsecs = 0; 054 055 private final String lockName; 056 private boolean locked = false; 057 private JbootRedis redis; 058 059 /** 060 * 创建redis分布式锁 061 * 062 * @param lockName 锁的名称 063 */ 064 public JbootRedisLock(String lockName) { 065 if (lockName == null) { 066 throw new NullPointerException("lockName must not be null."); 067 } 068 this.lockName = lockName; 069 this.redis = Jboot.getRedis(); 070 } 071 072 /** 073 * 创建redis分布式锁 074 * 075 * @param lockName 锁名称 076 * @param timeoutMsecs 获取锁的时候,等待时长 077 */ 078 public JbootRedisLock(String lockName, long timeoutMsecs) { 079 if (lockName == null) { 080 throw new NullPointerException("lockName must not be null."); 081 } 082 this.lockName = lockName; 083 this.timeoutMsecs = timeoutMsecs; 084 this.redis = Jboot.getRedis(); 085 } 086 087 088 /** 089 * @param lockName 锁名称 090 * @param timeoutMsecs 获取锁的时候,等待时长 091 * @param expireMsecs 超时时长 092 */ 093 public JbootRedisLock(String lockName, long timeoutMsecs, long expireMsecs) { 094 if (lockName == null) { 095 throw new NullPointerException("lockName must not be null."); 096 } 097 this.lockName = lockName; 098 this.timeoutMsecs = timeoutMsecs; 099 this.expireMsecs = expireMsecs; 100 101 this.redis = Jboot.getRedis(); 102 } 103 104 105 public long getTimeoutMsecs() { 106 return timeoutMsecs; 107 } 108 109 public void setTimeoutMsecs(long timeoutMsecs) { 110 this.timeoutMsecs = timeoutMsecs; 111 } 112 113 public long getExpireMsecs() { 114 return expireMsecs; 115 } 116 117 public void setExpireMsecs(long expireMsecs) { 118 this.expireMsecs = expireMsecs; 119 } 120 121 public void runIfAcquired(Runnable runnable) { 122 if (runnable == null) { 123 throw new NullPointerException("runnable must not null!"); 124 } 125 try { 126 if (acquire()) { 127 runnable.run(); 128 } 129 } finally { 130 //执行完毕,释放锁 131 release(); 132 } 133 } 134 135 136 /** 137 * 获取锁 138 * 139 * @return true:活动锁了 , false :没获得锁。 如果设置了timeoutMsecs,那么这个方法可能被延迟 timeoutMsecs 毫秒。 140 */ 141 public boolean acquire() { 142 long timeout = timeoutMsecs; 143 do { 144 try { 145 //超时时间 146 long expiredTime = System.currentTimeMillis() + expireMsecs + 1; 147 148 Long result = redis.setnx(lockName, expiredTime); 149 150 if (result == null) { 151 continue; 152 } 153 154 // lock acquired 155 if (result == 1) { 156 locked = true; 157 return true; 158 } 159 160 //之前的保存时间 161 Long savedValue = redis.get(lockName); 162 163 //这个锁已经过期了,此时可以去设置新的key 164 if (savedValue != null && savedValue < System.currentTimeMillis()) { 165 166 167 //获取上一个锁到期时间,并设置现在的锁到期时间, 168 //只有一获个线程才能取上一个线上的设置时间,因为jedis.getSet是同步的 169 Long oldValue = redis.getSet(lockName, expiredTime); 170 171 172 if (oldValue != null && oldValue.equals(savedValue)) { 173 //如果这个时候,多个线程恰好都到了这里 174 //只有一个线程的设置值和当前值相同,他才有权利获取锁 175 //lock acquired 176 locked = true; 177 return true; 178 } 179 } 180 } finally { 181 if (timeout > 0) { 182 timeout -= 100; 183 QuietlyUtil.sleepQuietly(100); 184 } 185 } 186 } while (timeout > 0); 187 188 return false; 189 } 190 191 192 /** 193 * 是否获得 锁 了 194 * 195 * @return 196 */ 197 public boolean isLocked() { 198 return locked; 199 } 200 201 /** 202 * 释放 锁 203 */ 204 public void release() { 205 if (!isLocked()) { 206 return; 207 } 208 if (Jboot.getRedis().del(lockName) > 0) { 209 locked = false; 210 } 211 } 212}