一、为什么要用分布式锁?
为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。
在分布式集群系统中,由于系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,变量之间不存在共享,也不具有可见性。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。
二、分布式锁应该具备的条件
1、互斥:在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁:减少锁等待的时间,避免导致大量线程阻塞;
4、具备可重入特性:同一个线程可以重复拿到同一个资源的锁;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
三、分布式锁的三种实现方式
1、基于数据库实现分布式锁
有两种细分实现:一种是利用主键唯一性,另一种是利用数据库排他锁。
利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
排他锁需要依赖数据库的行级锁,InnoDB 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,所以需要对查询字段进行创建唯一索引。而数据库是否使用索引会根据实际情况进行调整。
DROP TABLE IF EXISTS `method_lock`;CREATE TABLE `method_lock` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名', `state` tinyint NOT NULL COMMENT '1:未分配;2:已分配', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `version` int NOT NULL COMMENT '版本号', `PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
根据method_name获取到锁的信息,然后判断state字段值进行对应的修改。
缺点:
1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
解决方案: 1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。 3、非阻塞的?搞一个while循环,直到insert成功再返回成功。 4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
2、基于redis实现
(1)jedis实现
public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; 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, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 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) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; }}
上面方式实现分布式锁之后,就可以轻松解决大部分问题,但是仍然有些场景是不满足的,例如一个方法获取到锁之后,可能在方法内调这个方法此时就获取不到锁了。这个时候我们就需要把锁改进成可重入式锁了。
重入锁也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。可重入锁可以避免因同一线程中多次获取锁而导致死锁发生。像synchronized就是一个重入锁,它是通过moniter函数记录当前线程信息来实现的。实现可重入锁需要考虑两点:
获取锁:首先尝试获取锁,如果获取失败,判断这个锁是否是自己的,如果是则允许再次获取, 而且必须记录重复获取锁的次数。释放锁:释放锁不能直接删除了,因为锁是可重入的,如果锁进入了多次,在内层直接删除锁, 导致外部的业务在没有锁的情况下执行,会有安全问题。因此必须获取锁时累计重入的次数,释放时则减去重入次数,如果减到0,则可以删除锁。
假设锁的key为lock ,hashKey是当前线程的id(threadId),锁自动释放时间为20
获取锁的步骤:
1、判断lock是否存在 EXISTS lock
2、不存在,则自己获取锁,记录重入层数为1;
3、存在,锁已被获取。判断是不是自己,即判断当前线程id作为hashKey是否存在:HEXISTS lock threadId 。不存在,说明锁已经有了,且不是自己获取的,锁获取失败。存在,说明是自己获取的锁,重入次数+1:HINCRBY lock threadId 1 ,最后更新锁自动释放时间, EXPIRE lock 20
释放锁的步骤:
1、判断当前线程id作为hashKey是否存在:HEXISTS lock threadId
2、不存在,说明锁已经失效,不用管了
3、存在,说明锁还在,重入次数减1:HINCRBY lock threadId -1
4、获取新的重入次数,判断重入次数是否为0,为0说明锁全部释放,删除key:DEL lock
lock.lua:
local key = KEYS[1]; -- 第1个参数,锁的keylocal threadId = ARGV[1]; -- 第2个参数,线程唯一标识local releaseTime = ARGV[2]; -- 第3个参数,锁的自动释放时间if(redis.call('exists', key) == 0) then -- 判断锁是否已存在 redis.call('hset', key, threadId, '1'); -- 不存在, 则获取锁 redis.call('expire', key, releaseTime); -- 设置有效期 return 1; -- 返回结果end;if(redis.call('hexists', key, threadId) == 1) then -- 锁已经存在,判断threadId是否是自己 redis.call('hincrby', key, threadId, '1'); -- 如果是自己,则重入次数+1 redis.call('expire', key, releaseTime); -- 设置有效期 return 1; -- 返回结果end;return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
unlock.lua:
local key = KEYS[1]; -- 第1个参数,锁的keylocal threadId = ARGV[1]; -- 第2个参数,线程唯一标识if (redis.call('HEXISTS', key, threadId) == 0) then -- 判断当前锁是否还是被自己持有 return nil; -- 如果已经不是自己,则直接返回end;local count = redis.call('HINCRBY', key, threadId, -1); -- 是自己的锁,则重入次数-1if (count == 0) then -- 判断是否重入次数是否已经为0 redis.call('DEL', key); -- 等于0说明可以释放锁,直接删除 return nil; end;
import java.util.Collections;import java.util.UUID;import org.springframework.core.io.ClassPathResource;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.scripting.support.ResourceScriptSource;/** * Redis可重入锁 */public class RedisLock { private static final StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class); private static final DefaultRedisScript LOCK_SCRIPT; private static final DefaultRedisScript UNLOCK_SCRIPT; static { // 加载释放锁的脚本 LOCK_SCRIPT = new DefaultRedisScript<>(); LOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua"))); LOCK_SCRIPT.setResultType(Long.class); // 加载释放锁的脚本 UNLOCK_SCRIPT = new DefaultRedisScript<>(); UNLOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua"))); } /** * 获取锁 * @param lockName 锁名称 * @param releaseTime 超时时间(单位:秒) * @return key 解锁标识 */ public static String tryLock(String lockName,String releaseTime) { // 存入的线程信息的前缀,防止与其它JVM中线程信息冲突 String key = UUID.randomUUID().toString(); // 执行脚本 Long result = redisTemplate.execute( LOCK_SCRIPT, Collections.singletonList(lockName), key + Thread.currentThread().getId(), releaseTime); // 判断结果 if(result != null && result.intValue() == 1) { return key; }else { return null; } } /** * 释放锁 * @param lockName 锁名称 * @param key 解锁标识 */ public static void unlock(String lockName,String key) { // 执行脚本 redisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(lockName), key + Thread.currentThread().getId(), null); }}
(2)Redisson实现
1)加锁过程跟Jedis基本一致,获取到锁就执行lua脚本。
2)watch dog自动延期机制:设置的锁是有有效时间(防止死锁)的,假设锁的有效期比业务逻辑时间短,就会出现多个线程同时处理业务逻辑,失去了锁的意义,而且处理完业务逻辑时主动释放锁也会报错(此时得到锁的线程ID不一样了)。watch dog自动延期机制的作用就是监视线程和锁的过期时间,当线程还没有执行结束但是锁已经到期了就主动去延长锁的过期时间。
3)可重入锁:相同线程不需要在等待锁,提高性能。
Redisson分布式锁的实现是基于RLock接口,RedissonLock实现RLock接口。
先来看RLock接口
public interface RLock extends Lock, RExpirable, RLockAsync { /** * 中断锁 如果获得锁成功,添加锁的有效时间 * @param leaseTime 锁有效时间 * @param unit 时间单位 小时、分、秒、毫秒等 */ void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException; /** * 这里比Lock接口中的多一个参数,多添加一个锁的有效时间 * @param var1 等待时间 * @param var3 锁有效时间 * @param var5 时间单位 小时、分、秒、毫秒等 */ boolean tryLock(long var1, long var3, TimeUnit var5) throws InterruptedException; /** * 加锁 锁的有效期默认30秒 * @param leaseTime 锁有效时间 * @param unit 时间单位 小时、分、秒、毫秒等 */ void lock(long leaseTime , TimeUnit unit); void forceUnlock(); /** * 检验该锁是否被线程使用,如果被使用返回True */ boolean isLocked(); /** * 检查当前线程是否获得此锁(这个和上面的区别就是该方法可以判断是否当前线程获得此锁,而不是此锁是否被线程占有) * 这个比上面那个实用 */ boolean isHeldByCurrentThread(); int getHoldCount();}
RLock接口,主要是新添加了 leaseTime 属性字段,主要是用来设置锁的过期时间,避免死锁。
b.RedissonLock实现类
RedissonLock实现了RLock接口。
Redisson实现自定义注解式分布式锁starter:
源码地址:https://github.com/TaXueWWL/redis-distributed-lock
1)添加依赖
org.redisson redisson 3.5.4
2)Redisson分布式锁注解
@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})public @interface DistributedLock { /**分布式锁名称*/ String value() default "distributed-lock-redisson"; /**锁超时时间,默认十秒*/ int expireSeconds() default 10;}
3)Redisson加锁解锁操作类
public class RedissonLock { private static final Logger LOGGER = LoggerFactory.getLogger(RedissonLock.class); RedissonManager redissonManager; public RedissonLock(RedissonManager redissonManager) { this.redissonManager = redissonManager; } public RedissonLock() {} /** * 加锁操作 * @return */ public boolean lock(String lockName, long expireSeconds) { RLock rLock = redissonManager.getRedisson().getLock(lockName); boolean getLock = false; try { getLock = rLock.tryLock(0, expireSeconds, TimeUnit.SECONDS); if (getLock) { LOGGER.info("获取Redisson分布式锁[成功],lockName={}", lockName); } else { LOGGER.info("获取Redisson分布式锁[失败],lockName={}", lockName); } } catch (InterruptedException e) { LOGGER.error("获取Redisson分布式锁[异常],lockName=" + lockName, e); e.printStackTrace(); return false; } return getLock; } /** * 解锁 * @param lockName */ public void release(String lockName) { redissonManager.getRedisson().getLock(lockName).unlock(); }}
4)Redisson分布式锁注解解析器
@Aspect@Componentpublic class DistributedLockHandler { private static final Logger LOGGER = LoggerFactory.getLogger(DistributedLockHandler.class); @Pointcut("@annotation(com.snowalker.lock.redisson.annotation.DistributedLock)") public void distributedLock() {} @Autowired RedissonLock redissonLock; @Around("@annotation(distributedLock)") public void around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) { LOGGER.info("[开始]执行RedisLock环绕通知,获取Redis分布式锁开始"); /**获取锁名称*/ String lockName = distributedLock.value(); /**获取超时时间,默认十秒*/ int expireSeconds = distributedLock.expireSeconds(); if (redissonLock.lock(lockName, expireSeconds)) { try { LOGGER.info("获取Redis分布式锁[成功],加锁完成,开始执行业务逻辑..."); joinPoint.proceed(); } catch (Throwable throwable) { LOGGER.error("获取Redis分布式锁[异常],加锁失败", throwable); throwable.printStackTrace(); } finally { redissonLock.release(lockName); } LOGGER.info("释放Redis分布式锁[成功],解锁完成,结束业务逻辑..."); } else { LOGGER.error("获取Redis分布式锁[失败]"); } LOGGER.info("[结束]执行RedisLock环绕通知"); }}
5)初始化的redisson实例
public class RedissonManager { private static final Logger LOGGER = LoggerFactory.getLogger(Redisson.class); private Config config = new Config(); private Redisson redisson = null; public RedissonManager() {} public RedissonManager(RedissonProperties redissonProperties) { try { config = RedissonConfigFactory.getInstance().createConfig(redissonProperties); redisson = (Redisson) Redisson.create(config); } catch (Exception e) { LOGGER.error("Redisson init error", e); throw new IllegalArgumentException("please input correct configurations," + "connectionType must in standalone/sentinel/cluster/masterslave"); } } public Redisson getRedisson() { return redisson; } /** * Redisson连接方式配置工厂 * 双重检查锁 */ static class RedissonConfigFactory { private RedissonConfigFactory() {} private static volatile RedissonConfigFactory factory = null; public static RedissonConfigFactory getInstance() { if (factory == null) { synchronized (Object.class) { if (factory == null) { factory = new RedissonConfigFactory(); } } } return factory; } private Config config = new Config(); /** * 根据连接类型获取对应连接方式的配置,基于策略模式 * @param redissonProperties * @return Config */ Config createConfig(RedissonProperties redissonProperties) { Preconditions.checkNotNull(redissonProperties); Preconditions.checkNotNull(redissonProperties.getAddress(), "redisson.lock.server.address cannot be NULL!"); Preconditions.checkNotNull(redissonProperties.getType(), "redisson.lock.server.password cannot be NULL"); Preconditions.checkNotNull(redissonProperties.getDatabase(), "redisson.lock.server.database cannot be NULL"); String connectionType = redissonProperties.getType(); /**声明配置上下文*/ RedissonConfigContext redissonConfigContext = null; if (connectionType.equals(RedisConnectionType.STANDALONE.getConnection_type())) { redissonConfigContext = new RedissonConfigContext(new StandaloneRedissonConfigStrategyImpl()); } else if (connectionType.equals(RedisConnectionType.SENTINEL.getConnection_type())) { redissonConfigContext = new RedissonConfigContext(new SentinelRedissonConfigStrategyImpl()); } else if (connectionType.equals(RedisConnectionType.CLUSTER.getConnection_type())) { redissonConfigContext = new RedissonConfigContext(new ClusterRedissonConfigStrategyImpl()); } else if (connectionType.equals(RedisConnectionType.MASTERSLAVE.getConnection_type())) { redissonConfigContext = new RedissonConfigContext(new MasterslaveRedissonConfigStrategyImpl()); } else { throw new IllegalArgumentException("创建Redisson连接Config失败!当前连接方式:" + connectionType); } return redissonConfigContext.createRedissonConfig(redissonProperties); } }}
6)创建AutoConfiguration类,开启自动配置
@Configuration@ConditionalOnClass(Redisson.class)@EnableConfigurationProperties(RedissonProperties.class)public class RedissonAutoConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(RedissonAutoConfiguration.class); @Bean @ConditionalOnMissingBean @Order(value = 2) public RedissonLock redissonLock(RedissonManager redissonManager) { RedissonLock redissonLock = new RedissonLock(); redissonLock.setRedissonManager(redissonManager); LOGGER.info("[RedissonLock]组装完毕"); return redissonLock; } @Bean @ConditionalOnMissingBean @Order(value = 1) public RedissonManager redissonManager(RedissonProperties redissonProperties) { RedissonManager redissonManager = new RedissonManager(redissonProperties); LOGGER.info("[RedissonManager]组装完毕,当前连接方式:" + redissonProperties.getType() + ",连接地址:" + redissonProperties.getAddress()); return redissonManager; }}
7)添加自动装配支持
在resources下建立一个目录,名为META-INF , 并在其中定义一个文件,名为spring.factories,并在其中添加如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.snowalker.lock.redisson.config.RedissonAutoConfiguration
这样之后编译打包可以生成直接引入项目中的jar包使用。
上面的代码只列举了创建starter的关键步骤的代码,完整代码可以查看github原作者的代码。
https://github.com/TaXueWWL/redis-distributed-lock
8)使用有两种方式:注解方式和编程式
8.1)注解式
直接在对应的方法上添加我们定义的注解
@Scheduled(cron = "${redis.lock.cron}") @DistributedLock(value = "redis-lock", expireSeconds = 11) public void execute() throws InterruptedException { LOGGER.info("[ExecutorRedisson]--执行定时任务开始,休眠三秒"); Thread.sleep(3000); System.out.println("=======================业务逻辑============================="); LOGGER.info("[ExecutorRedisson]--执行定时任务结束,休眠三秒");}
原作者的源码中添加了开启注解支持,如下:
没有必要。只要在spring.factories文件中添加了autoconfiguration类就可以,项目启动时@SpringBootApplication注解会自动扫描并添加注解支持。
8.2)编程式
在需要加锁的业务逻辑类中,注入 RedissonLock 实体。
@AutowiredRedissonLock redissonLock; @Scheduled(cron = "${redis.lock.cron}")public void execute() throws InterruptedException { if (redissonLock.lock("redisson", 10)) { LOGGER.info("[ExecutorRedisson]--执行定时任务开始,休眠三秒"); Thread.sleep(3000); System.out.println("=======业务逻辑==============="); LOGGER.info("[ExecutorRedisson]--执行定时任务结束,休眠三秒"); redissonLock.release("redisson"); } else { LOGGER.info("[ExecutorRedisson]获取锁失败"); }}
实际开发中Maven仓库已经有redisson-spring-boot-starter,直接引用:
org.redisson redisson-spring-boot-starter 3.13.6
---------------------优美的暂时分割线-------------------------
Redisson提供了多种分布式锁:
1. 可重入锁(Reentrant Lock)
Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。
public void testReentrantLock(RedissonClient redisson){ RLock lock = redisson.getLock("anyLock"); try{ // 1. 最常见的使用方法 //lock.lock(); // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁 //lock.lock(10, TimeUnit.SECONDS); // 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS); if(res){ //成功 // do your business } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
Redisson同时还为分布式锁提供了异步执行的相关方法:
public void testAsyncReentrantLock(RedissonClient redisson){ RLock lock = redisson.getLock("anyLock"); try{ lock.lockAsync(); lock.lockAsync(10, TimeUnit.SECONDS); Future res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS); if(res.get()){ // do your business } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { lock.unlock(); } }
2. 公平锁(Fair Lock)
Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
public void testFairLock(RedissonClient redisson){ RLock fairLock = redisson.getFairLock("anyLock"); try{ // 最常见的使用方法 fairLock.lock(); // 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁 fairLock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { fairLock.unlock(); } }
Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:
RLock fairLock = redisson.getFairLock("anyLock");fairLock.lockAsync();fairLock.lockAsync(10, TimeUnit.SECONDS);Future res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
3. 联锁(MultiLock)
Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
public void testMultiLock(RedissonClient redisson1, RedissonClient redisson2, RedissonClient redisson3){ RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); try { // 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。 lock.lock(); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
4. 红锁(RedLock)
Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock
对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。
public void testRedLock(RedissonClient redisson1, RedissonClient redisson2, RedissonClient redisson3){ RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); try { // 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。 lock.lock(); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
5. 读写锁(ReadWriteLock)
Redisson的分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持自动过期解锁。该对象允许同时有多个读取锁,但是最多只能有一个写入锁。
RReadWriteLock rwlock = redisson.getLock("anyRWLock");// 最常见的使用方法rwlock.readLock().lock();// 或rwlock.writeLock().lock();// 支持过期解锁功能// 10秒钟以后自动解锁// 无需调用unlock方法手动解锁rwlock.readLock().lock(10, TimeUnit.SECONDS);// 或rwlock.writeLock().lock(10, TimeUnit.SECONDS);// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);// 或boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);...lock.unlock();