diff --git a/src/main/java/tech/smartboot/redisun/Redisun.java b/src/main/java/tech/smartboot/redisun/Redisun.java index 87ad8fe28f88988ce7c887a4a6e3c8d99bfe0a1c..f2a5787703afd84d025cbdd462b290e542efc5ce 100644 --- a/src/main/java/tech/smartboot/redisun/Redisun.java +++ b/src/main/java/tech/smartboot/redisun/Redisun.java @@ -1338,4 +1338,96 @@ public final class Redisun { throw new RedisunException(e); } } + + /** + * 尝试获取分布式锁 + * + * @param name 锁名称 + * @param maxLockTime 最长锁定时间(秒) + * @param retryTime 重试时间(秒) + * @return 锁标识符,如果获取锁成功则返回锁标识符,否则返回 null + */ + public String lock(String name, int maxLockTime, double retryTime) { + // 生成一个唯一的锁标识符(UUID),作为锁的值存储在 Redis 中 + String lockId = java.util.UUID.randomUUID().toString(); + // Set 设置NX选项:仅在键不存在时设置键;EX选项:设置键的过期时间(秒) + SetCommand cmd = new SetCommand(name, lockId).setIfNotExists().expire(maxLockTime); + long startTime = System.currentTimeMillis(); + long sleepTime = 50; + try { + // 循环尝试获取锁,直到成功或超时 + do { + if (execute(cmd).thenApply(SET_CMD_FUTURE).get()) { + // 成功获取锁,返回锁标识符 + return lockId; + } + // 等待一下 + lightWait(sleepTime); + sleepTime = Math.min(sleepTime * 2, 1000); + // 继续循环的条件:尚未超时(当前时间 - 开始时间 < 重试时间) + } while ((System.currentTimeMillis() - startTime) < (retryTime * 1000)); + } catch (Throwable e) { + throw new RedisunException(e); + } + return null; + } + + /** + * 释放锁 + * + * @param name 锁名称 + * @param lockId 锁标识符 + */ + public void unlock(String name, String lockId) { + String val = get(name); + if (val != null && val.equals(lockId)) { + del(name); + } + } + + /** + * 尝试获取分布式锁并执行任务 + * + * @param name 锁名称 + * @param maxLockTime 最长锁定时间(秒) + * @param retryTime 重试时间(秒) + * @param fn 任务函数 + * @return 任务执行结果 + */ + public boolean withLock(String name, int maxLockTime, double retryTime, Runnable fn) { + String lockId = lock(name, maxLockTime, retryTime); + if (lockId == null) { + return false; + } + try { + fn.run(); + return true; + } finally { + unlock(name, lockId); + } + } + + /** + * 尝试获取分布式锁并执行任务 + * + * @param name 锁名称 + * @param maxLockTimeAndRetryTime 最长锁定时间(秒)和重试时间(秒) + * @param fn 任务函数 + * @return 任务执行结果 + */ + public boolean withLock(String name, double maxLockTimeAndRetryTime, Runnable fn) { + return withLock(name, (int) Math.ceil(maxLockTimeAndRetryTime), maxLockTimeAndRetryTime, fn); + } + + /** + * 轻量级等待:毫秒内的短时间等待场景,自旋 + Thread.yield() + * @param millis 等待时间(毫秒) + */ + private void lightWait(long millis) { + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < millis) { + Thread.yield(); + } + } + } diff --git a/src/test/java/tech/smartboot/redisun/test/RedisunTest.java b/src/test/java/tech/smartboot/redisun/test/RedisunTest.java index 278d2e64342a32361c9fc0b65e0a4a4577e9b2bf..869b4be2207d54bc6eb50598df9164c06944c23f 100644 --- a/src/test/java/tech/smartboot/redisun/test/RedisunTest.java +++ b/src/test/java/tech/smartboot/redisun/test/RedisunTest.java @@ -1539,4 +1539,26 @@ public class RedisunTest { System.out.println("=== 测试总结 ==="); System.out.println("模式订阅测试通过!"); } + + @Test + public void testLock() throws InterruptedException { + String lockName = "lock:" + topic; + String lockId = redisun.lock(lockName, 10, 1000); + Assert.assertNotNull(lockId); + Assert.assertEquals(1, redisun.exists(lockName)); + redisun.unlock(lockName, lockId); + Assert.assertEquals(0, redisun.exists(lockName)); + for (int i = 0; i < 10; i++) { + // 让一些任务超时 获取不到执行权 + new Thread(() -> redisun.withLock(lockName, 1, 5, () -> { + System.out.println("任务开始执行:" + Thread.currentThread().getName()); + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { } + System.out.println("任务执行完毕" + Thread.currentThread().getName()); + }), "withLock_" + i).start(); + } + Thread.sleep(10000); + System.out.println("任务执行完毕"); + } }