基于SpringBoot實現(xiàn)分布式鎖的三種方法
我來詳細(xì)講解Spring Boot中實現(xiàn)分布式鎖的幾種方式,包括手寫Redis鎖和使用Redisson框架。
一、基于Redis原生命令實現(xiàn)分布式鎖
1. 基礎(chǔ)版Redis分布式鎖
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "distributed:lock:";
private static final long DEFAULT_EXPIRE_TIME = 30000; // 30秒
private static final long DEFAULT_WAIT_TIME = 10000; // 10秒
private static final long DEFAULT_SLEEP_TIME = 100; // 100ms
/**
* 嘗試獲取分布式鎖(簡單版)
* @param lockKey 鎖的key
* @param value 鎖的值(通常用UUID)
* @param expireTime 鎖的過期時間(ms)
* @return 是否獲取成功
*/
public boolean tryLock(String lockKey, String value, long expireTime) {
String key = LOCK_PREFIX + lockKey;
// 使用SET命令,通過NX參數(shù)實現(xiàn)"不存在時設(shè)置",EX參數(shù)設(shè)置過期時間
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 釋放鎖
* 需要確保是自己加的鎖才能釋放(防止釋放別人的鎖)
*/
public boolean releaseLock(String lockKey, String value) {
String key = LOCK_PREFIX + lockKey;
String currentValue = redisTemplate.opsForValue().get(key);
// 通過Lua腳本確保原子性:判斷值是否匹配,匹配則刪除
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(key),
value
);
return result != null && result == 1;
}
/**
* 獲取鎖(支持重試)
*/
public boolean lockWithRetry(String lockKey, String value,
long expireTime, long waitTime) {
long endTime = System.currentTimeMillis() + waitTime;
while (System.currentTimeMillis() < endTime) {
if (tryLock(lockKey, value, expireTime)) {
return true;
}
try {
Thread.sleep(DEFAULT_SLEEP_TIME);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
return false;
}
}
2. 可重入鎖實現(xiàn)
@Component
public class RedisReentrantLock {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "reentrant:lock:";
private static final ThreadLocal<Map<String, Integer>> LOCK_COUNT =
ThreadLocal.withInitial(HashMap::new);
/**
* 可重入鎖實現(xiàn)
*/
public boolean tryReentrantLock(String lockKey, String clientId,
long expireTime) {
String key = LOCK_PREFIX + lockKey;
Map<String, Integer> lockCountMap = LOCK_COUNT.get();
int count = lockCountMap.getOrDefault(lockKey, 0);
// 重入:當(dāng)前線程已持有鎖
if (count > 0) {
lockCountMap.put(lockKey, count + 1);
return true;
}
// 嘗試獲取鎖
String luaScript =
"if redis.call('exists', KEYS[1]) == 0 then " +
" redis.call('hset', KEYS[1], 'owner', ARGV[1]) " +
" redis.call('hset', KEYS[1], 'count', 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 1 " +
"elseif redis.call('hget', KEYS[1], 'owner') == ARGV[1] then " +
" local count = redis.call('hget', KEYS[1], 'count') " +
" redis.call('hset', KEYS[1], 'count', tonumber(count) + 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
clientId,
String.valueOf(expireTime)
);
if (result != null && result == 1) {
lockCountMap.put(lockKey, 1);
return true;
}
return false;
}
/**
* 釋放可重入鎖
*/
public boolean releaseReentrantLock(String lockKey, String clientId) {
String key = LOCK_PREFIX + lockKey;
Map<String, Integer> lockCountMap = LOCK_COUNT.get();
int count = lockCountMap.getOrDefault(lockKey, 0);
if (count <= 0) {
return false;
}
if (count > 1) {
// 重入次數(shù)減1
lockCountMap.put(lockKey, count - 1);
return true;
}
// 最后一次重入,釋放鎖
String luaScript =
"if redis.call('hget', KEYS[1], 'owner') == ARGV[1] then " +
" local current = redis.call('hget', KEYS[1], 'count') " +
" if tonumber(current) > 1 then " +
" redis.call('hset', KEYS[1], 'count', tonumber(current) - 1) " +
" redis.call('pexpire', KEYS[1], ARGV[2]) " +
" return 0 " +
" else " +
" redis.call('del', KEYS[1]) " +
" return 1 " +
" end " +
"else " +
" return -1 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(
script,
Collections.singletonList(key),
clientId,
"30000"
);
if (result != null && result == 1) {
lockCountMap.remove(lockKey);
if (lockCountMap.isEmpty()) {
LOCK_COUNT.remove();
}
return true;
}
return false;
}
}
二、使用Redisson實現(xiàn)分布式鎖(推薦生產(chǎn)環(huán)境使用)
1. 添加依賴
<!-- pom.xml -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>
2. 配置Redisson
# application.yml
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 3000
redisson:
config: |
singleServerConfig:
address: "redis://${spring.redis.host}:${spring.redis.port}"
database: ${spring.redis.database}
connectionPoolSize: 64
connectionMinimumIdleSize: 24
idleConnectionTimeout: 10000
connectTimeout: ${spring.redis.timeout}
timeout: ${spring.redis.timeout}
retryAttempts: 3
retryInterval: 1500
3. Redisson分布式鎖服務(wù)
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedissonDistributedLock {
@Autowired
private RedissonClient redissonClient;
/**
* 可重入鎖(最常用)
*/
public boolean tryReentrantLock(String lockKey, long waitTime,
long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 公平鎖
*/
public boolean tryFairLock(String lockKey, long waitTime,
long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getFairLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 聯(lián)鎖(多個鎖同時獲?。?
*/
public boolean tryMultiLock(String[] lockKeys, long waitTime,
long leaseTime, TimeUnit unit) {
RLock[] locks = new RLock[lockKeys.length];
for (int i = 0; i < lockKeys.length; i++) {
locks[i] = redissonClient.getLock(lockKeys[i]);
}
RLock multiLock = redissonClient.getMultiLock(locks);
try {
return multiLock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 紅鎖(RedLock,多個Redis實例)
*/
public boolean tryRedLock(String lockKey, long waitTime,
long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 釋放鎖
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* 強(qiáng)制釋放鎖
*/
public void forceUnlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.forceUnlock();
}
/**
* 自動續(xù)期的鎖(看門狗機(jī)制)
*/
public void lockWithWatchdog(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(); // 默認(rèn)30秒,看門狗會自動續(xù)期
}
}
4. 使用AOP簡化分布式鎖使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/** 鎖的key,支持SpEL表達(dá)式 */
String key();
/** 鎖類型,默認(rèn)可重入鎖 */
LockType lockType() default LockType.REENTRANT;
/** 等待時間(秒) */
long waitTime() default 5;
/** 持有時間(秒),-1表示使用看門狗自動續(xù)期 */
long leaseTime() default -1;
/** 時間單位 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
enum LockType {
REENTRANT, // 可重入鎖
FAIR, // 公平鎖
READ, // 讀鎖
WRITE, // 寫鎖
MULTI, // 聯(lián)鎖
RED // 紅鎖
}
}
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedissonDistributedLock redissonDistributedLock;
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint,
DistributedLock distributedLock) throws Throwable {
String lockKey = parseKey(distributedLock.key(), joinPoint);
RLock lock = getLock(lockKey, distributedLock.lockType());
boolean locked = false;
try {
// 嘗試獲取鎖
if (distributedLock.leaseTime() == -1) {
// 使用看門狗自動續(xù)期
lock.lock();
} else {
locked = lock.tryLock(
distributedLock.waitTime(),
distributedLock.leaseTime(),
distributedLock.timeUnit()
);
}
if (!locked && distributedLock.leaseTime() != -1) {
throw new RuntimeException("獲取分布式鎖失敗: " + lockKey);
}
log.info("獲取分布式鎖成功: {}", lockKey);
return joinPoint.proceed();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("獲取分布式鎖被中斷", e);
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("釋放分布式鎖: {}", lockKey);
}
}
}
private String parseKey(String keySpEL, ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 解析SpEL表達(dá)式
if (keySpEL.startsWith("#")) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keySpEL);
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("methodName", method.getName());
// 設(shè)置參數(shù)
Object[] args = joinPoint.getArgs();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
context.setVariable(parameters[i].getName(), args[i]);
}
return expression.getValue(context, String.class);
}
return keySpEL;
}
private RLock getLock(String lockKey, DistributedLock.LockType lockType) {
switch (lockType) {
case FAIR:
return redissonClient.getFairLock(lockKey);
case READ:
return redissonClient.getReadWriteLock(lockKey).readLock();
case WRITE:
return redissonClient.getReadWriteLock(lockKey).writeLock();
case MULTI:
case RED:
// 簡化處理,實際使用需要多個實例
return redissonClient.getLock(lockKey);
case REENTRANT:
default:
return redissonClient.getLock(lockKey);
}
}
}
三、使用Spring Integration實現(xiàn)分布式鎖
1. 添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
2. 配置Redis鎖注冊表
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory connectionFactory) {
// 過期時間10秒
return new RedisLockRegistry(connectionFactory, "distributed-lock", 10000L);
}
@Bean
public LockRegistry lockRegistry(RedisLockRegistry redisLockRegistry) {
return redisLockRegistry;
}
}
3. 使用Spring Integration鎖
@Service
@Slf4j
public class OrderService {
@Autowired
private LockRegistry lockRegistry;
public void createOrder(String orderId) {
Lock lock = lockRegistry.obtain("order:" + orderId);
boolean locked = false;
try {
// 嘗試獲取鎖,最多等待5秒
locked = lock.tryLock(5, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("系統(tǒng)繁忙,請稍后重試");
}
// 執(zhí)行業(yè)務(wù)邏輯
processOrder(orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("訂單處理被中斷", e);
} finally {
if (locked) {
lock.unlock();
}
}
}
private void processOrder(String orderId) {
// 訂單處理邏輯
log.info("處理訂單: {}", orderId);
}
}
四、實際應(yīng)用示例
1. 商品秒殺場景
@Service
@Slf4j
public class SeckillService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private StringRedisTemplate redisTemplate;
private static final String SECKILL_PREFIX = "seckill:product:";
private static final String LOCK_PREFIX = "seckill:lock:";
/**
* 秒殺下單(防超賣)
*/
public boolean seckillOrder(Long productId, Integer quantity, Long userId) {
String lockKey = LOCK_PREFIX + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 嘗試獲取鎖,等待100ms,持有鎖3秒
if (!lock.tryLock(100, 3000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("搶購太火爆,請重試");
}
// 檢查庫存
String stockKey = SECKILL_PREFIX + productId + ":stock";
Integer stock = Integer.valueOf(
redisTemplate.opsForValue().get(stockKey)
);
if (stock == null || stock < quantity) {
throw new RuntimeException("庫存不足");
}
// 扣減庫存
Long newStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
if (newStock < 0) {
// 庫存不足,恢復(fù)庫存
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new RuntimeException("庫存不足");
}
// 創(chuàng)建訂單
createOrder(productId, quantity, userId);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("系統(tǒng)異常", e);
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 使用注解簡化版
*/
@DistributedLock(
key = "'seckill:lock:' + #productId",
waitTime = 1,
leaseTime = 3,
timeUnit = TimeUnit.SECONDS
)
public boolean seckillOrderWithAnnotation(Long productId, Integer quantity, Long userId) {
// 業(yè)務(wù)邏輯,無需關(guān)心鎖的獲取和釋放
String stockKey = SECKILL_PREFIX + productId + ":stock";
Integer stock = Integer.valueOf(
redisTemplate.opsForValue().get(stockKey)
);
if (stock == null || stock < quantity) {
throw new RuntimeException("庫存不足");
}
Long newStock = redisTemplate.opsForValue().decrement(stockKey, quantity);
if (newStock < 0) {
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new RuntimeException("庫存不足");
}
createOrder(productId, quantity, userId);
return true;
}
private void createOrder(Long productId, Integer quantity, Long userId) {
// 創(chuàng)建訂單邏輯
log.info("用戶{}搶購商品{},數(shù)量{}", userId, productId, quantity);
}
}
2. 定時任務(wù)防重復(fù)執(zhí)行
@Component
@Slf4j
public class ScheduledTasks {
@Autowired
private RedissonClient redissonClient;
/**
* 分布式定時任務(wù),確保集群中只有一個實例執(zhí)行
*/
@Scheduled(cron = "0 */5 * * * ?") // 每5分鐘執(zhí)行一次
public void syncDataTask() {
String lockKey = "task:sync:data";
RLock lock = redissonClient.getLock(lockKey);
// 不等待,獲取不到鎖直接返回
boolean locked = lock.tryLock();
if (!locked) {
log.info("其他節(jié)點正在執(zhí)行數(shù)據(jù)同步任務(wù)");
return;
}
try {
log.info("開始執(zhí)行數(shù)據(jù)同步任務(wù)");
// 執(zhí)行業(yè)務(wù)邏輯
syncData();
log.info("數(shù)據(jù)同步任務(wù)完成");
} finally {
lock.unlock();
}
}
private void syncData() {
// 數(shù)據(jù)同步邏輯
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
五、配置和最佳實踐
1. Redisson配置類
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password:}")
private String password;
@Value("${spring.redis.database:0}")
private int database;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 單節(jié)點模式
config.useSingleServer()
.setAddress(String.format("redis://%s:%s", redisHost, redisPort))
.setDatabase(database)
.setPassword(StringUtils.hasText(password) ? password : null)
.setConnectionPoolSize(64)
.setConnectionMinimumIdleSize(10)
.setIdleConnectionTimeout(10000)
.setConnectTimeout(3000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500)
.setPingConnectionInterval(30000)
.setKeepAlive(true);
// 鎖配置
config.setLockWatchdogTimeout(30000L); // 看門狗超時時間
return Redisson.create(config);
}
}
2. 分布式鎖工具類
@Component
@Slf4j
public class DistributedLockUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 執(zhí)行帶鎖的方法
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
locked = lock.tryLock(waitTime, leaseTime, unit);
if (!locked) {
throw new DistributedLockException("獲取分布式鎖失敗: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new DistributedLockException("獲取分布式鎖被中斷", e);
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 執(zhí)行帶鎖的方法(無返回值)
*/
public void executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Runnable runnable) {
executeWithLock(lockKey, waitTime, leaseTime, unit, () -> {
runnable.run();
return null;
});
}
/**
* 執(zhí)行帶鎖的方法(快速失敗)
*/
public <T> Optional<T> tryExecuteWithLock(String lockKey, long waitTime,
long leaseTime, TimeUnit unit,
Supplier<T> supplier) {
try {
return Optional.ofNullable(
executeWithLock(lockKey, waitTime, leaseTime, unit, supplier)
);
} catch (DistributedLockException e) {
log.warn("獲取鎖失敗,跳過執(zhí)行: {}", lockKey);
return Optional.empty();
}
}
public static class DistributedLockException extends RuntimeException {
public DistributedLockException(String message) {
super(message);
}
public DistributedLockException(String message, Throwable cause) {
super(message, cause);
}
}
}
六、測試分布式鎖
@SpringBootTest
@Slf4j
class DistributedLockTest {
@Autowired
private RedissonDistributedLock redissonDistributedLock;
@Autowired
private DistributedLockUtil distributedLockUtil;
private final AtomicInteger counter = new AtomicInteger(0);
@Test
void testConcurrentLock() throws InterruptedException {
int threadCount = 10;
String lockKey = "test:concurrent:lock";
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
boolean locked = redissonDistributedLock
.tryReentrantLock(lockKey, 2, 5, TimeUnit.SECONDS);
if (locked) {
try {
// 模擬業(yè)務(wù)處理
Thread.sleep(100);
int value = counter.incrementAndGet();
log.info("線程 {} 獲取鎖成功,計數(shù): {}",
Thread.currentThread().getName(), value);
} finally {
redissonDistributedLock.unlock(lockKey);
}
} else {
log.warn("線程 {} 獲取鎖失敗", Thread.currentThread().getName());
}
} catch (Exception e) {
log.error("線程執(zhí)行異常", e);
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
assertEquals(threadCount, counter.get());
}
@Test
void testLockWithUtil() {
String lockKey = "test:util:lock";
String result = distributedLockUtil.executeWithLock(
lockKey,
2,
5,
TimeUnit.SECONDS,
() -> {
// 業(yè)務(wù)邏輯
return "success";
}
);
assertEquals("success", result);
}
}
總結(jié)與建議
1.選擇方案
- 簡單場景:使用Spring Integration的
RedisLockRegistry - 生產(chǎn)環(huán)境:推薦使用Redisson,功能最全,穩(wěn)定性最好
- 特殊需求:需要精細(xì)控制時,可以使用原生Redis命令自定義
2.最佳實踐
- 鎖的key要有業(yè)務(wù)含義,如
order:create:{orderId} - 一定要設(shè)置合理的過期時間,防止死鎖
- 釋放鎖時要檢查是否當(dāng)前線程持有
- 使用Lua腳本保證原子性
- 考慮鎖的可重入性
- 生產(chǎn)環(huán)境使用Redis集群或哨兵模式
3.注意事項
- 避免鎖粒度過大,影響并發(fā)性能
- 避免鎖持有時間過長
- 實現(xiàn)鎖的自動續(xù)期(看門狗機(jī)制)
- 考慮鎖等待超時和快速失敗
- 添加監(jiān)控和告警機(jī)制
4.常見問題解決
- 鎖提前過期:使用Redisson的看門狗機(jī)制
- 鎖誤刪:每個鎖設(shè)置唯一value,釋放時驗證
- 鎖不可重入:使用Redisson或?qū)崿F(xiàn)可重入邏輯
- Redis集群故障:使用RedLock算法(多個Redis實例)
這樣實現(xiàn)的分布式鎖既安全又可靠,可以滿足大多數(shù)業(yè)務(wù)場景的需求。
以上就是基于SpringBoot實現(xiàn)分布式鎖的三種方法的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot分布式鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot使用Zookeeper實現(xiàn)分布式鎖的實現(xiàn)示例
- SpringBoot項目中實現(xiàn)Redis分布式鎖的方法與最佳實踐
- SpringBoot中使用Zookeeper實現(xiàn)分布式鎖的案例
- SpringBoot3+Redis實現(xiàn)分布式鎖的配置方法
- Springboot中使用Redis實現(xiàn)分布式鎖的示例代碼
- Springboot使用redisson實現(xiàn)分布式鎖的代碼示例
- 基于SpringBoot+Redis實現(xiàn)分布式鎖
- 如何在SpringBoot中使用Redis實現(xiàn)分布式鎖
- SpringBoot整合分布式鎖redisson的示例代碼
相關(guān)文章
java使用Logback配置輸出日志內(nèi)容到文件示例代碼
這篇文章主要介紹了java?Logback輸出日志內(nèi)容到文件,要將logger.info的信息輸出到文件,您可以使用Logback配置,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
java中實現(xiàn)OCR文字提取的幾個常用工具(Tesseract、PaddleOCR及RapidOCR)
Java最強(qiáng)的OCR識別是指在Java開發(fā)環(huán)境下,具有強(qiáng)大OCR(Optical Character Recognition,光學(xué)字符識別)功能的工具或庫,這篇文章主要介紹了java中實現(xiàn)OCR文字提取的幾種方法,分別是Tesseract、PaddleOCR及RapidOCR,需要的朋友可以參考下2025-09-09
IDEA JetBrains Mono字體介紹和安裝教程(詳解)
這篇文章主要介紹了IDEA JetBrains Mono字體介紹和安裝教程,本給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
spring boot實戰(zhàn)之本地jar包引用示例
本篇文章主要介紹了spring boot實戰(zhàn)之本地jar包引用示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
MyBatis實現(xiàn)數(shù)據(jù)庫類型和Java類型的轉(zhuǎn)換
MyBatis 在處理數(shù)據(jù)庫查詢結(jié)果或傳遞參數(shù)時,需要將數(shù)據(jù)庫類型與 Java 類型之間進(jìn)行轉(zhuǎn)換,本文就給大家介紹MyBatis如何實現(xiàn)數(shù)據(jù)庫類型和 Java 類型的轉(zhuǎn)換的,需要的朋友可以參考下2024-09-09
CompletableFuture創(chuàng)建及功能使用全面詳解
這篇文章主要為大家介紹了CompletableFuture創(chuàng)建及功能使用全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

