自己实现锁时一般要满足以下几个条件:
1、锁要互斥
2、锁要可重入
3、加锁操作要提供阻塞和非阻塞两种模式
4、提供锁释放操作
5、锁要具备失效机制,避免死锁
用redis的几个简单命令就能实现分布式锁:
1、setnx
setnx key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0
2、expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁
3、del
del key:删除key
直接上代码:
import java.util.concurrent.TimeUnit; public interface DistributeLock { /** * 加锁操作,等待直到获取锁成功或者线程被中断 */ void lock() throws InterruptedException; /** * 尝试获取锁,获取失败直接返回 * @return * @throws InterruptedException */ boolean tryLock() throws InterruptedException; /** * 尝试获取锁,等待到超时或者线程被中断 * @param time * @param unit * @return * @throws InterruptedException */ boolean tryLock(long time, TimeUnit unit) throws InterruptedException; /** * 释放锁 */ void unlock(); }
import lombok.extern.slf4j.Slf4j; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; @Slf4j public class RedisDistributeLock implements DistributeLock { /** * 锁名称 */ private final String lockName; /** * 自动解锁时间, 单位秒 */ private final int expireTime; /** * UUID */ private final String uuid; /** * 线程本地变量, 用于锁重入检查 */ private ThreadLocal<AtomicInteger> locks = new ThreadLocal<AtomicInteger>(); /** * Jedis连接池 */ private static JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); // 设置最大连接数 config.setMaxTotal(200); // 设置最大空闲数 config.setMaxIdle(8); // 设置最大等待时间 config.setMaxWaitMillis(1000 * 100); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000); } public RedisDistributeLock(String lockName, int expireTime) { this.lockName = lockName; this.expireTime = expireTime; uuid = UUID.randomUUID().toString(); } public void lock() throws InterruptedException { Jedis jedis = null; try { jedis = jedisPool.getResource(); // 锁重入检查 if (reentrant(jedis)) { return; } while (true) { if (jedis.setnx(lockName, uuid) == 1) { jedis.expire(lockName, expireTime); locks.set(new AtomicInteger(1)); return; } LockSupport.parkNanos(this, 50); if (Thread.interrupted()) { throw new InterruptedException(); } } } catch (InterruptedException e) { throw new InterruptedException(); } catch (Exception e) { log.error("lock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid); } finally { if (jedis != null) { jedis.close(); } } } @Override public boolean tryLock() { Jedis jedis = null; try { jedis = jedisPool.getResource(); // 锁重入检查 if (reentrant(jedis)) { return true; } if (jedis.setnx(lockName, uuid) == 1) { jedis.expire(lockName, expireTime); locks.set(new AtomicInteger(1)); return true; } return false; } catch (Exception e) { log.error("tryLock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid); return false; } finally { if (jedis != null) { jedis.close(); } } } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { Jedis jedis = null; try { jedis = jedisPool.getResource(); // 锁重入检查 if (reentrant(jedis)) { return true; } long timeout = System.nanoTime() + unit.toNanos(time); while (true) { if (jedis.setnx(lockName, uuid) == 1) { jedis.expire(lockName, expireTime); locks.set(new AtomicInteger(1)); return true; } if (System.nanoTime() >= timeout) { return false; } LockSupport.parkNanos(this, 50); if (Thread.interrupted()) { throw new InterruptedException(); } } } catch (InterruptedException e) { throw new InterruptedException(); } catch (Exception e) { log.error("tryLock exception, lockName = {}, expireTime = {}, uuid = {}", lockName, expireTime, uuid); return false; } finally { if (jedis != null) { jedis.close(); } } } public void unlock() { Jedis jedis = null; try { AtomicInteger atomicInteger = locks.get(); if (atomicInteger == null) { throw new IllegalMonitorStateException("Attempting to unlock without first obtaining that lock on this thread"); } int lockCounts = atomicInteger.decrementAndGet(); if (lockCounts == 0) { locks.remove(); jedis = jedisPool.getResource(); if (jedis.get(lockName).equals(uuid)) { jedis.del(lockName); } } } catch (Exception e) { log.error("unlock exception"); } finally { if (jedis != null) { jedis.close(); } } } /** * 锁重入检查 * * @return 重入则返回 true; 否则返回 false */ private boolean reentrant(Jedis jedis) { try { if (locks.get() != null) { locks.get().incrementAndGet(); // reentrant, refresh lease time jedis.expire(lockName, expireTime); return true; } else { return false; } } catch (Exception e) { throw new RuntimeException("Redis refresh lease time failed, key: " + lockName, e); } } }