使用redisson
1.pom依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>${version}</version> </dependency>
2.配置对应redisson
@Configuration
public class RedissonConfig {
@Autowired(required = false)
RedissonProperties redissonProperties;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient() {
if (Objects.nonNull(redissonProperties)) {
Config config = new Config();
String[] nodes = redissonProperties.getSentinelNodes().split(",");
SentinelServersConfig sentinelServersConfig = config.useSentinelServers()
.setMasterName(redissonProperties.getMasterName())
.setDatabase(redissonProperties.getDatabase())
.setConnectTimeout(redissonProperties.getConnectTimeout())
.setPassword(redissonProperties.getPassword());
for (String node : nodes) {
sentinelServersConfig = sentinelServersConfig.addSentinelAddress("redis://" + node);
}
return Redisson.create(config);
} else {
return null;
}
}
}
3.使用
这里是官方文档
1) 使用普通锁(可重入锁)
注意:可重入锁lock()几次,就要对应的unlock()几次
public static void main(String[] args) {
RedissonClient redisson = new RedissonClient;
RLock lock = redisson.getLock("lock");
lock.lock(2, TimeUnit.SECONDS);
Thread t = new Thread(() -> {
RLock lock1 = redisson.getLock("lock");
lock1.lock();
lock1.unlock();
});
t.start();
t.join();
lock.unlock();
}
2) 使用公平锁
公平锁秉持先到先得原则,先请求获取锁的线程先获取到锁,后来的线程等待。
@Autowired
private RedissonClient redisson;
public void demo() {
RLock lock = redisson.getLock("lock");
lock.lock(2, TimeUnit.SECONDS);
Thread t = new Thread(() -> {
RLock lock1 = redisson.getLock("lock");
lock1.lock();
lock1.unlock();
});
t.start();
t.join();
lock.unlock();
}
3) 使用读写锁
@Autowired
private RedissonClient redisson;
public void demo() throws InterruptedException {
final RReadWriteLock lock = redisson.getReadWriteLock("lock");
lock.writeLock().tryLock();
Thread t = new Thread() {
public void run() {
RLock r = lock.readLock();
r.lock(2, TimeUnit.SECONDS);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.unlock();
};
};
t.start();
t.join();
lock.writeLock().unlock();
t.join();
}
4) 使用红锁
使用redlock需要多个redissonClient,多个redissonClient需要多个互相独立的哨兵,我们的项目里目前只有一个哨兵,所以暂不推荐使用红锁
上面是4种比较常用的分布式锁机制,我们针对不同业务作出不同选型。
redisson将各种锁的概念与java的各种锁完美的结合在一起,封装做的非常巧妙,甚至对于CountDownLatch、Semaphore、Atomic等等也有很多精妙绝伦的封装,感兴趣的同学可以研究一下官方文档。
注意:1. 不管使用哪一种锁,都需要设置锁的过期时间。
2. 使用lock()、tryLock()时,一定要放在try{}catch(){}中,且unlock一定要放在finally{}里
3. 所有需要使用分布式锁场景的锁名,都要使用枚举、常量统一管理
redisson锁原理
1) tryLock()表示尝试加锁,加锁成功返回true,加锁失败返回false。使用tryLock()加锁,线程没有获取到锁不会阻塞。
使用tryLock()加锁流程:
2) lock() 加锁,此锁已被持有则等待,没有被持有则获取这把锁。使用lock()加锁,线程没有获取到锁会一直阻塞直到获取到锁。
使用lock()加锁流程:
3) 使用可重入锁、公平锁、读写锁时,执行的lua脚本各不相同。
类型 | 加锁lua | 解锁lua |
---|---|---|
可重入锁 红锁 | ![]() |
![]() |
公平锁 | ![]() |
![]() |
读锁 | ![]() |
![]() |
写锁 | ![]() |
![]() |
4)tryLock()与lock()的使用场景
- 场景一: 库存抢占。有效库存只有100,多个应用来抢占这100个库存总数。这时使用:lock()
- 场景二: 退款申请。网络卡顿或其他原因同时段提交多次退款申请,实际只生成一条退款单。这时使用:tryLock()
5)tryLock()与lcok()使用的注意事项
- tryLock() 尝试获取锁,不等待,返回获取结果。
- tryLock(long waitTime, TimeUnit timeUnit) 尝试获取锁,并等待waitTime个时间单位,之后返回获取结果。
- tryLock(long waitTime, long leaseTime, TimeUnit timeUnit) 尝试获取锁,并等待waitTIme个时间单位,并将此锁持有leaseTime个时间单位,之后返回获取结果。
- lock() 获取锁,线程阻塞,直到获取到锁为止,锁不过期,直到解锁或者被内部机制释放。
- lock(long leaseTime, TimeUnit timeUnit) 获取锁,直到获取到锁为止,持有锁leaseTime个时间单位,之后自动释放锁,也可以提前手动释放。
6)公平锁、可重入锁、读写锁、红锁的使用场景
-
场景一: 门票只有100张。先到先得。这时使用公平锁
-
场景二: 1. 门票只有100张,需要抢票。2. 需要把下一张门票修改成vip票。 (可重入锁是在一个线程中多次获取同一把锁。一个线程在执行一个带锁的方法时,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁)
-
场景三: 所有人都能看到库存信息,但同时只有一个人来修改库存。所有的人都能看到库存信息,使用读锁。同时只有一个人来修改库存,使用写锁。多个读锁不互斥,读锁与写锁互斥
-
场景四: 当需要超级高可用锁的场景时,使用红锁。
综上所述,使用tryLock还是lock,使用公平锁还是读写锁都是看具体的业务和场景而言。