我们使用redis 实现分布式锁,因为redis是单线程的,因此我们不必考虑并发安全问题
1.加锁
setnx lock volue //返回1代表lock这个键不存在,即获取到锁,返回0代表锁有人在用
2.解锁
del lock
3.设置过期时间
如果在删除锁之前客户端宕机,锁就永远无法删除,所以我们可以设置过期时间
也可以引入watch-dog本质是一个守护线程,只要客户端不宕机,又没有释放锁就给锁续期
@Component
public class RedisService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
JedisPool jedisPool;
// 获取锁之前的超时时间(获取锁的等待重试时间)
private long acquireTimeout = 5000;
// 获取锁之后的超时时间(防止死锁)
private int timeOut = 10000;
/**
* 获取分布式锁
* @return 锁标识
*/
public boolean getRedisLock(String lockName,String val) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
// 1.计算获取锁的时间
Long endTime = System.currentTimeMillis() + acquireTimeout;
// 2.尝试获取锁
while (System.currentTimeMillis() < endTime) {
// 3. 获取锁成功就设置过期时间
if (jedis.setnx(lockName, val) == 1) {
jedis.expire(lockName, timeOut/1000);
return true;
}
}
} catch (Exception e) {
log.error(e.getMessage());
} finally {
returnResource(jedis);
}
return false;
}
/**
* 释放分布式锁
* @param lockName 锁名称
*/
public void unRedisLock(String lockName) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
// 释放锁
jedis.del(lockName);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
returnResource(jedis);
}
}
但此时仍然可能出现问题! 如果A获取锁后 超时了,锁被释放,B获取到锁,但A如果此时删除了锁,及时B没有释放锁 ,C也可能获得锁,因此我们需要判断是否删除的是自己的锁,我们可以在value中存入当前线程的唯一标识
/**
* 第二种分布式锁
*/
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//保证获取锁和设置锁时间的原子性
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
if (jedis.get(lockKey).equals(requestId)) {
System.out.println("释放锁..." + Thread.currentThread().getName() + ",identifierValue:" + requestId);
jedis.del(lockKey);
return true;
}
return false;
}
}
这里还有问题,就是如果我们在判断是不是自己锁的时候,由于redis与服务器之间通信有延迟,所以可能在传输结果过程中锁过期了,自动释放了,但是判断结果传输过来了通过了,我们删除的还不是自己的锁,因此判断+删除锁应该也是一个原子性的操作,我们需要使用Lua脚本
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
Redisson是redis推出的解决redis在分布式情况下的一些问题的一个框架,对分布式锁也有比较好的支持
1.导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
2.编写配置类,返回RedissonClient
@Configuration
public class MyRedissonConfig{
@Bean(destroyMethod="shutdown")
public RedissionClient redisson() throws IOException{
//创建配置
Config config=new Config();
//我这里是使用单机模式,注意必须加上redis://,如果redis启用了安全连接则必须为rediss://
config.useSingleServer().setAddress("redis://localhost:6379");
//返回RedissionClient
return Redisson.create(config);
}
}
也可以通过yml文件的方式
Config config = Config.fromYAML(new File("config-file.yaml"));
RedissonClient redisson = Redisson.create(config);
3.测试
可重入锁:某个线程在已经获取锁的情况下可以再次获取锁
@Autowird
RedissonClient redissonClient;
public void test(){
RLock lock=redission.getLock("lock1"); //括号里为锁的名字,只要锁名一样就是同一把锁
lock.lock();//加锁,会自璇尝试获取锁,锁的默认时间是30s
try{
Thread.sleep(30000);
}catch (Exception e){
}finally{
lock.unlock();//解锁
}
}
Redisson中有一个看门狗机制,可以实现
1.对锁的自动续期,如果业务时间较长,锁会自动续期(看门狗时间/3 续一次)
2.加锁的业务只要自动完成,就不会给当前锁续期,即使出现异常没有手动解锁,锁的默认时间是30s
如果是手动设置锁的过期时间,则不能自动续期
lock.lock(10,TimeUnit.SECONDS);//此时过期时间一定要大于业务时间
即使锁过期了,业务没执行完,也不会删除不是自己的锁(会基于Lua脚本自动判断锁是不是自己的)
4.其他锁
//公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁,类似于排队吃饭。
RLock fairLock = redissonClient.getFairLock("anyLock");
//连锁:基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
lock.lock();// 所有的锁都上锁成功才算成功。
...
lock.unlock();
//红锁:从多个独立的Redis节点中获取锁,只有获得绝大多数锁才算获得锁
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();// 红锁在大部分节点上加锁成功就算成功。
...
lock.unlock();
//读写锁(**推荐**):读共有,写独占,读需要等待写
RReadWriteLock rwlock = redissonClient.getReadWriteLock("anyRWLock");
rwlock.readLock().lock();// 最常见的使用方法
rwlock.writeLock().lock();
//信号量:限制同时访问的线程数量
RSemaphore semaphore=redissonClient.getSemaphore("semaphore");
semaphore.trySetPermits(5); //设置信号量
semaphore.acquire();//占用一个坑位,阻塞式获取
boolean exits=semaphore.tryAcquire();//获取不到就返回false,可以用作限流
semaphore.release();//释放一个坑位
//闭锁:分布式CountDownLatch,等待其他线程完成后再继续
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(5);
latch.await();
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");// 在其他线程或其他JVM中
latch.countDown();
原文:https://www.cnblogs.com/wangstudyblog/p/14726556.html