直接上代码
中间存在一个bug,可能导致死锁.
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import redis.clients.jedis.Jedis;import java.util.Objects;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;/** * Created by wenshiliang on 2017/6/5. * 不太可靠的基于redis的分布式锁 * setnx一个跟随jvm的uuid到redis,成功,认为占用锁成功,设置thread. * * 释放锁:判断是不是当前thread占用的,判断是不是当前jvm占用的,是,del redis key. * * * bug: 当异常退出jvm时,可能导致lock未释放.一般情况下设置了redis value的有效时长.如果在setnx后,expire前异常中断了jvm.可能会导致死锁. */public class RedisLock { public static final int LOCK_DEATH_TIME = 300;//lock锁住时长,防止死锁,单位seconds private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class); private final static String UID = UUID.randomUUID().toString(); private final static ConcurrentHashMapLOCK_MAP = new ConcurrentHashMap<>(); private RedisTemplate redisTemplate; private Thread exclusiveOwnerThread; private String key; static { //钩子,退出jvm时,移除lock Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println("redis lock shutdown hook"); LOCK_MAP.entrySet().forEach(entry -> { entry.getKey().forceRelease(); }); })); } public RedisLock(RedisTemplate redisTemplate, String key) { this.redisTemplate = redisTemplate; this.key = key; } public void acquire() { int i=1; while (!acquire(1000)){ if(i++>300){ LOGGER.warn("lock acquire wait"); } }; } /** * 获得锁 * @param time 毫秒 * @return */ public boolean acquire(final long time) { if (Objects.equals(Thread.currentThread(), exclusiveOwnerThread)) { String result = redisTemplate.opsForValue().get(key); if (UID.equals(result)) { return true; } setExclusiveOwnerThread(null); } boolean flag = false; long start = System.currentTimeMillis(); while (!flag && start <= System.currentTimeMillis() + time) { flag = redisTemplate.execute(new RedisCallback () { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { Jedis jedis = (Jedis) connection.getNativeConnection(); if (jedis.setnx(key, UID) == 1L) { jedis.expire(key, LOCK_DEATH_TIME);//300秒过期,防止死锁.如果在这步前jvm挂了,会导致一直死锁. LOCK_MAP.put(RedisLock.this,1); setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } }); } return flag; } public void release() { if (Objects.equals(Thread.currentThread(), exclusiveOwnerThread)) { String result = redisTemplate.opsForValue().get(key); if (UID.equals(result)) { LOCK_MAP.remove(this); setExclusiveOwnerThread(null); redisTemplate.delete(key); } } } protected void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } private void forceRelease() { String result = redisTemplate.opsForValue().get(key); if (UID.equals(result)) { LOCK_MAP.remove(this); setExclusiveOwnerThread(null); redisTemplate.delete(key); LOGGER.info("force release lock: " + key); } }}