目录
集群下一人一单的线程并发安全问题:
集群模式下用synchronized并不能解决并发安全问题,因为集群模式下是多个JVM,对应的是多个锁监视器。
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
实现分布式锁需要实现两个基本的方法:
上述做法会出现的问题:当线程一业务阻塞锁提前释放,线程二开始工作,线程一恢复后完成业务释放锁,释放的是线程二的锁,锁被线程三拿到。
解决办法:释放锁的时候去判断标识,是不是自己所拥有的锁。
- public boolean tryLock(long timeoutSec) {
- // 获取线程标示
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 获取锁
- Boolean success = stringRedisTemplate.opsForValue()
- .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
- return Boolean.TRUE.equals(success);
- }
- public void unlock() {
- // 获取线程标示
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 获取锁中的标示
- String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
- // 判断标示是否一致
- if(threadId.equals(id)) {
- // 释放锁
- stringRedisTemplate.delete(KEY_PREFIX + name);
- }
- }
这一次的线程一的阻塞发生在判断完标识之后线程一执行释放锁之前,接着超时释放锁,线程二获取锁,此时线程一恢复,因为前面已经判断了标识,所以可以释放锁,导致线程三可以趁虚而入
解决办法:判断完标识和释放锁成为原子性的操作
- private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
- static {
- UNLOCK_SCRIPT = new DefaultRedisScript<>();
- UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
- UNLOCK_SCRIPT.setResultType(Long.class);
- }
-
- @Override
- public boolean tryLock(long timeoutSec) {
- // 获取线程标示
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 获取锁
- Boolean success = stringRedisTemplate.opsForValue()
- .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
- return Boolean.TRUE.equals(success);
- }
-
- @Override
- public void unlock() {
- // 调用lua脚本
- stringRedisTemplate.execute(
- UNLOCK_SCRIPT,
- Collections.singletonList(KEY_PREFIX + name),
- ID_PREFIX + Thread.currentThread().getId());
- }
- -- 比较线程标示与锁中的标示是否一致
- if(redis.call('get', KEYS[1]) == ARGV[1]) then
- -- 释放锁 del key
- return redis.call('del', KEYS[1])
- end
- return 0
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
- @Bean
- public RedissonClient redissonClient(){
- // 配置
- Config config = new Config();
- config.useSingleServer().setAddress("redis://192.168.226.128:6379").setPassword("123456");
- // 创建RedissonClient对象
- return Redisson.create(config);
- }
问题出现情况如上述一般:在方法A中获取了锁,同时调用了方法B,在方法B又要获取锁,导致死锁问题
参考Java中的renentrelock:
- local key = KEYS[1]; -- 锁的key
- local threadId = ARGV[1]; -- 线程唯一标识
- local releaseTime = ARGV[2]; -- 锁的自动释放时间
- -- 判断是否存在
- if(redis.call('exists', key) == 0) then
- -- 不存在, 获取锁
- redis.call('hset', key, threadId, '1');
- -- 设置有效期
- redis.call('expire', key, releaseTime);
- return 1; -- 返回结果end;
- -- 锁已经存在,判断threadId是否是自己
- if(redis.call('hexists', key, threadId) == 1) then
- -- 不存在, 获取锁,重入次数+1
- redis.call('hincrby', key, threadId, '1');
- -- 设置有效期
- redis.call('expire', key, releaseTime);
- return 1; -- 返回结果end;
- return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
- local key = KEYS[1]; -- 锁的key
- local threadId = ARGV[1]; -- 线程唯一标识
- local releaseTime = ARGV[2]; -- 锁的自动释放时间
- -- 判断当前锁是否还是被自己持有
- if (redis.call('HEXISTS', key, threadId) == 0) then
- return nil; -- 如果已经不是自己,则直接返回end;
- -- 是自己的锁,则重入次数-1
- local count = redis.call('HINCRBY', key, threadId, -1);
- -- 判断是否重入次数是否已经为0
- if (count > 0) then
- -- 大于0说明不能释放锁,重置有效期然后返回
- redis.call('EXPIRE', key, releaseTime);
- return nil;
- else -- 等于0说明可以释放锁,直接删除
- redis.call('DEL', key);
- return nil;
- end;
解决问题: