锁的种类
jvm进程锁和分布式锁
jvm进程锁
说明:jvm进程锁可以控制jvm内部多个线程的共享资源访问。常用的有synchronized和Lock ,异同点如下:
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
2、对异常的处理:
synchronized除了在流程走完释放锁,在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生
Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock 时需要在finally块中释放锁
3、中断锁:
Lock可以让等待锁的线程响应中断,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到锁,只有进行等待的情况下,是可以响应中断的。而使用synchronized时,等待的线程会一直等待下去,不能响应中断。
4、获取到锁的通知:Lock可以知道有没有成功获取锁,而synchronized却无法办到
5、ReentrantLock和synchronized都是可重入锁
分布式锁
一、常用的分布式锁
基于数据库实现分布式锁
基于缓存(Redis/Redisson)实现分布式锁
基于Zookeeper实现分布式锁
二、分布式锁的条件
1、排他性:同一时间,只能有一个客户端获取锁,其他客户端不能同时获取锁
2、避免死锁:这把锁在一定的时间后需要释放,否则会产生死锁,这里面包括正常释放锁和非正常释放锁,比如即使一个客户端在持锁期间发生故障而没有释放锁也要保证后续的客户端能加锁
3、自己解锁:加锁和解锁都应该是同一个客户端去完成,不能释放别人的锁
4、可用性
三、数据库分布式锁
在数据库中创建一个表,并在字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
四、Redisson分布式锁
实现思路:
获取锁时,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
可重入的原理:
使用hash的数据结构,存储当前锁的key下对应的每个线程的持有锁数量
五、Zookeeper分布式锁
定义锁:通过Zookeeper上的数据节点来表示一个锁,如/exclusive_lock/lock节点即可定义为一个锁
获取锁:需要获取锁时,所有客户端会试图通过调用create()接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。zookeeper会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获得了锁。同时,所有没有获得锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到持有锁节点的变更情况
释放锁:以下两种情况会释放锁:
当前持有锁的客户端发生宕机,Zookeeper上的这个临时节点会被移除
正常执行完业务逻辑后,客户端主动将自己创建的临时节点删除
六、spring全局锁
springboot全局锁依赖
对应的实现方式的依赖 Redis/Zookeeper
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<!-- spring integration,实现redis分布式锁 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
配置
Redis/Zookeeper连接配置
spring:
#Redis 连接
redis: ***
依赖的实现方式的Configuration配置
@Configuration
public class RedisConfig {
// 当前业务场景下,要加的锁的前缀,如对同一个seq操作加锁,则生成的锁的key:push_lock_seq_6166234
public static final String LOCK_KEY_PRE = "push_lock_seq_";
@Bean
public LockRegistry lockRegistry(LettuceConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, LOCK_KEY_PRE);
}
}
使用
@Component
public class Operation {
/**
* 注入配置类中配置的bean
*/
@Autowired
private LockRegistry lockRegistry;
/**
* 业务逻辑
*/
public void operation(){
// ...
Lock lock = lockRegistry.obtain(RedisConfig.LOCK_KEY_PRE); // 获取锁对象
lock.lock(); // 加锁,同步请求锁,如不设置,则默认的获取锁的阻塞时间为60S
try {
// TODO operation
} finally {
lock.unlock(); // 释放锁
}
// ...
}
}
原理:
SpringIntegretion的优点
-
SpringIntegretion实现了本地锁+Redis锁的组合,减少了同一个锁对Redis的压力
-
锁工厂基于SpringDataRedis, 只需一份配置
SpringIntegretion的缺点
-
未实现看门狗
-
仅支持非公平锁,不支持公平锁、RedLock、ReadLock、WriteLock
-
锁工厂基于SpringDataRedis, 如果分布式锁服务器的Redis实例和缓存服务分离配置会比较麻烦
如果同一个KEY的锁并发量比较大,且没有锁类型要求,可以考虑使用SpringIntegration,否则推荐使用Redisson