
1.Lock4j是什么?
1.1简介
1.2项目地址
https://gitee.com/baomidou/lock4j/tree/v2.2.6/1.3 我之前手写的分布式锁和限流的实现
https://mp.weixin.qq.com/s/_MX4K_zXc2AbuvN-YrCzoAhttps://blog.csdn.net/qq_34905631/article/details/139033796?spm=1001.2014.3001.5501
2.特性
简单易用,功能强大,扩展性强。
支持redission,redisTemplate,zookeeper。可混用,支持扩展。
3.如何使用
3.1引入相关依赖
<dependencies><dependency><groupId>com.baomidougroupId><artifactId>lock4j-redis-template-spring-boot-starterartifactId><version>${latest.version}version>dependency><dependency><groupId>com.baomidougroupId><artifactId>lock4j-redisson-spring-boot-starterartifactId><version>${latest.version}version>dependency><dependency><groupId>com.baomidougroupId><artifactId>lock4j-zookeeper-spring-boot-starterartifactId><version>${latest.version}version>dependency>dependencies>
3.2 配置redis或zookeeper
spring:redis:host: 127.0.0.1...coordinate:zookeeper:zkServers: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
配置全局默认的获取锁超时时间和锁过期时间。
lock4j:acquire-timeout: 3000expire: 30000primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutorlock-key-prefix: lock4j
3.3 使用方式
3.3.1 注解式自动式
public class DemoService {//默认获取锁超时3秒,30秒锁过期4jpublic void simple() {//do something}//完全配置,支持spel4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)public User customMethod(User user) {return user;}}
3.3.2 手动式
public class ProgrammaticService {private LockTemplate lockTemplate;public void programmaticLock(String userId) {// 各种查询操作 不上锁// ...// 获取锁final LockInfo lockInfo = lockTemplate.lock(userId, 30000L, 5000L, RedissonLockExecutor.class);if (null == lockInfo) {throw new RuntimeException("业务处理中,请稍后再试");}// 获取锁成功,处理业务try {System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));} finally {//释放锁lockTemplate.releaseLock(lockInfo);}//结束}}
4.源码解析
4.1项目目录

4.2实现思路

三种实现如下:

/** Copyright (c) 2018-2022, baomidou (63976799@qq.com).** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.baomidou.lock;import com.baomidou.lock.exception.LockException;import com.baomidou.lock.executor.LockExecutor;import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;import com.baomidou.lock.util.LockUtil;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.InitializingBean;import org.springframework.util.Assert;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.concurrent.TimeUnit;/**** 锁模板方法*** @author zengzhihong TaoYu*/("rawtypes")4jpublic class LockTemplate implements InitializingBean {private final Map, LockExecutor> executorMap = new LinkedHashMap<>(); private Lock4jProperties properties;private Listexecutors; private LockExecutor primaryExecutor;public LockTemplate() {}public LockInfo lock(String key) {return lock(key, 0, -1);}public LockInfo lock(String key, long expire, long acquireTimeout) {return lock(key, expire, acquireTimeout, null);}/*** 加锁方法** @param key 锁key 同一个key只能被一个客户端持有* @param expire 过期时间(ms) 防止死锁* @param acquireTimeout 尝试获取锁超时时间(ms)* @param executor 执行器* @return 加锁成功返回锁信息 失败返回null*/public LockInfo lock(String key, long expire, long acquireTimeout, Class extends LockExecutor> executor) {acquireTimeout = acquireTimeout < 0 ? properties.getAcquireTimeout() : acquireTimeout;long retryInterval = properties.getRetryInterval();LockExecutor lockExecutor = obtainExecutor(executor);log.debug(String.format("use lock class: %s", lockExecutor.getClass()));expire = !lockExecutor.renewal() && expire <= 0 ? properties.getExpire() : expire;int acquireCount = 0;String value = LockUtil.simpleUUID();long start = System.currentTimeMillis();try {do {acquireCount++;Object lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);if (null != lockInstance) {return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance,lockExecutor);}TimeUnit.MILLISECONDS.sleep(retryInterval);} while (System.currentTimeMillis() - start < acquireTimeout);} catch (InterruptedException e) {log.error("lock error", e);throw new LockException();}return null;}("unchecked")public boolean releaseLock(LockInfo lockInfo) {if (null == lockInfo) {return false;}return lockInfo.getLockExecutor().releaseLock(lockInfo.getLockKey(), lockInfo.getLockValue(),lockInfo.getLockInstance());}protected LockExecutor obtainExecutor(Class extends LockExecutor> clazz) {if (null == clazz || clazz == LockExecutor.class) {return primaryExecutor;}final LockExecutor lockExecutor = executorMap.get(clazz);Assert.notNull(lockExecutor, String.format("can not get bean type of %s", clazz));return lockExecutor;}public void afterPropertiesSet() throws Exception {Assert.isTrue(properties.getAcquireTimeout() >= 0, "tryTimeout must least 0");Assert.isTrue(properties.getExpire() >= -1, "expireTime must lease -1");Assert.isTrue(properties.getRetryInterval() >= 0, "retryInterval must more than 0");Assert.hasText(properties.getLockKeyPrefix(), "lock key prefix must be not blank");Assert.notEmpty(executors, "executors must have at least one");for (LockExecutor executor : executors) {executorMap.put(executor.getClass(), executor);}final Class extends LockExecutor> primaryExecutor = properties.getPrimaryExecutor();if (null == primaryExecutor) {this.primaryExecutor = executors.get(0);} else {this.primaryExecutor = executorMap.get(primaryExecutor);Assert.notNull(this.primaryExecutor, "primaryExecutor must be not null");}}}
/** Copyright (c) 2018-2022, baomidou (63976799@qq.com).** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.baomidou.lock.aop;import com.baomidou.lock.LockFailureStrategy;import com.baomidou.lock.LockInfo;import com.baomidou.lock.LockKeyBuilder;import com.baomidou.lock.LockTemplate;import com.baomidou.lock.annotation.Lock4j;import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;import lombok.Builder;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.aop.framework.AopProxyUtils;import org.springframework.beans.factory.InitializingBean;import org.springframework.core.OrderComparator;import org.springframework.core.Ordered;import org.springframework.core.annotation.AnnotatedElementUtils;import org.springframework.util.StringUtils;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.stream.Collectors;/*** 分布式锁aop处理器** @author zengzhihong TaoYu*/@Slf4j@RequiredArgsConstructorpublic class LockInterceptor implements MethodInterceptor,InitializingBean {private final Map<Class extends LockKeyBuilder>, LockKeyBuilder> keyBuilderMap = new LinkedHashMap<>();private final Map<Class extends LockFailureStrategy>, LockFailureStrategy> failureStrategyMap = new LinkedHashMap<>();private final LockTemplate lockTemplate;private final List<LockKeyBuilder> keyBuilders;private final List<LockFailureStrategy> failureStrategies;private final Lock4jProperties lock4jProperties;private LockOperation primaryLockOperation;@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//fix 使用其他aop组件时,aop切了两次.Class> cls = AopProxyUtils.ultimateTargetClass(invocation.getThis());if (!cls.equals(invocation.getThis().getClass())) {return invocation.proceed();}Lock4j lock4j = AnnotatedElementUtils.findMergedAnnotation(invocation.getMethod(),Lock4j.class);LockInfo lockInfo = null;try {LockOperation lockOperation = buildLockOperation(lock4j);String prefix = lock4jProperties.getLockKeyPrefix() + ":";prefix += StringUtils.hasText(lock4j.name()) ? lock4j.name() :invocation.getMethod().getDeclaringClass().getName() + invocation.getMethod().getName();String key = prefix + "#" + lockOperation.lockKeyBuilder.buildKey(invocation, lock4j.keys());lockInfo = lockTemplate.lock(key, lock4j.expire(), lock4j.acquireTimeout(), lock4j.executor());if (null != lockInfo) {return invocation.proceed();}// lock failurelockOperation.lockFailureStrategy.onLockFailure(key, invocation.getMethod(), invocation.getArguments());return null;} finally {if (null != lockInfo && lock4j.autoRelease()) {final boolean releaseLock = lockTemplate.releaseLock(lockInfo);if (!releaseLock) {log.error("releaseLock fail,lockKey={},lockValue={}", lockInfo.getLockKey(),lockInfo.getLockValue());}}}}@Overridepublic void afterPropertiesSet(){keyBuilderMap.putAll(keyBuilders.stream().collect(Collectors.toMap(LockKeyBuilder::getClass, x -> x)));failureStrategyMap.putAll(failureStrategies.stream().collect(Collectors.toMap(LockFailureStrategy::getClass, x -> x)));LockKeyBuilder lockKeyBuilder;LockFailureStrategy lockFailureStrategy;List<LockKeyBuilder> priorityOrderedLockBuilders = keyBuilders.stream().filter(Ordered.class::isInstance).collect(Collectors.toList());if (lock4jProperties.getPrimaryKeyBuilder() != null) {lockKeyBuilder = keyBuilderMap.get(lock4jProperties.getPrimaryKeyBuilder());} else if (!priorityOrderedLockBuilders.isEmpty()) {sortOperation(priorityOrderedLockBuilders);lockKeyBuilder = priorityOrderedLockBuilders.get(0);} else {lockKeyBuilder = keyBuilders.get(0);}List<LockFailureStrategy> priorityOrderedFailures = failureStrategies.stream().filter(Ordered.class::isInstance).collect(Collectors.toList());if (lock4jProperties.getPrimaryFailureStrategy() != null) {lockFailureStrategy = failureStrategyMap.get(lock4jProperties.getPrimaryFailureStrategy());} else if (!priorityOrderedFailures.isEmpty()) {sortOperation(priorityOrderedFailures);lockFailureStrategy = priorityOrderedFailures.get(0);} else {lockFailureStrategy = failureStrategies.get(0);}primaryLockOperation = LockOperation.builder().lockKeyBuilder(lockKeyBuilder).lockFailureStrategy(lockFailureStrategy).build();}@Builderprivate static class LockOperation{/*** key生成器*/private LockKeyBuilder lockKeyBuilder;/*** 锁失败策略*/private LockFailureStrategy lockFailureStrategy;}private LockOperation buildLockOperation(Lock4j lock4j){LockKeyBuilder lockKeyBuilder;LockFailureStrategy lockFailureStrategy;Class extends LockFailureStrategy> failStrategy = lock4j.failStrategy();Class extends LockKeyBuilder> keyBuilderStrategy = lock4j.keyBuilderStrategy();if (keyBuilderStrategy == null || keyBuilderStrategy == LockKeyBuilder.class) {lockKeyBuilder = primaryLockOperation.lockKeyBuilder;} else {lockKeyBuilder = keyBuilderMap.get(keyBuilderStrategy);}if (failStrategy == null || failStrategy == LockFailureStrategy.class) {lockFailureStrategy = primaryLockOperation.lockFailureStrategy;} else {lockFailureStrategy = failureStrategyMap.get(failStrategy);}return LockOperation.builder().lockKeyBuilder(lockKeyBuilder).lockFailureStrategy(lockFailureStrategy).build();}private void sortOperation(List> operations){if (operations.size()<=1){return;}operations.sort(OrderComparator.INSTANCE);}}
public LockTemplate lockTemplate(Listexecutors) {LockTemplate lockTemplate = new LockTemplate();lockTemplate.setProperties(properties);lockTemplate.setExecutors(executors);return lockTemplate;}
@Setterprivate Listexecutors;
public void afterPropertiesSet() throws Exception {Assert.isTrue(properties.getAcquireTimeout() >= 0, "tryTimeout must least 0");Assert.isTrue(properties.getExpire() >= -1, "expireTime must lease -1");Assert.isTrue(properties.getRetryInterval() >= 0, "retryInterval must more than 0");Assert.hasText(properties.getLockKeyPrefix(), "lock key prefix must be not blank");Assert.notEmpty(executors, "executors must have at least one");for (LockExecutor executor : executors) {executorMap.put(executor.getClass(), executor);}final Class extends LockExecutor> primaryExecutor = properties.getPrimaryExecutor();if (null == primaryExecutor) {this.primaryExecutor = executors.get(0);} else {this.primaryExecutor = executorMap.get(primaryExecutor);Assert.notNull(this.primaryExecutor, "primaryExecutor must be not null");}}
LockExecutor lockExecutor = obtainExecutor(executor);private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript<>("return redis.call('set',KEYS[1]," +"ARGV[1],'NX','PX',ARGV[2])", String.class);private static final RedisScript<String> SCRIPT_UNLOCK = new DefaultRedisScript<>("if redis.call('get',KEYS[1]) " +"== ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class);private static final RedisScript<Boolean> SCRIPT_RENEWAL = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) ==ARGV[1] " +"then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end", Boolean.class);