From b7c503a2567f7addf5069be05afbbfb7038e3749 Mon Sep 17 00:00:00 2001 From: wwzg99 Date: Sun, 8 Jun 2025 21:34:59 +0800 Subject: [PATCH] =?UTF-8?q?LockUtil=E7=B1=BB=E4=B8=AD=E6=96=B0=E5=A2=9Etry?= =?UTF-8?q?Lock=20lock=E9=9D=99=E6=80=81=E6=96=B9=E6=B3=95=E5=92=8CLockSta?= =?UTF-8?q?t=E8=BE=85=E5=8A=A9=E7=B1=BB=EF=BC=8C=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E9=94=81=E7=9A=84=E8=87=AA=E5=8A=A8=E5=8A=A0=E9=94=81=E5=92=8C?= =?UTF-8?q?=E8=A7=A3=E9=94=81=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/thread/lock/LockUtil.java | 72 ++++++ .../hutool/core/thread/lock/LockUtilTest.java | 228 ++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 hutool-core/src/test/java/cn/hutool/core/thread/lock/LockUtilTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java index 61a0fdcfc..f63d5c9c5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.thread.lock; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -140,4 +141,75 @@ public class LockUtil { public static Lock getLazySegmentLock(int segments, Object key) { return SegmentLock.lazyWeakLock(segments).get(key); } + + + /** + * 下面 tryLock 和 lock 静态方法和辅助类LockStat 实现锁的自动加锁和解锁的封装 + * 使用方法如下 + *
+	 * Lock myLock = new ReentrantLock();
+	 *
+	 * // 尝试获取锁,并设置超时时间
+	 * try (LockStat lockStat = LockUtils.tryLock(myLock, 5, TimeUnit.SECONDS)) {
+	 *     if (lockStat.isLocked()) {
+	 *         // Critical section - lock acquired successfully
+	 *         doSomethingCritical();
+	 *     } else {
+	 *         // Handle case where lock was not acquired
+	 *         handleLockTimeout();
+	 *     }
+	 * } // Lock 会自动释放
+	 *
+	 * // 获取锁,不设置超时时间
+	 * try (LockStat lockStat = LockUtils.lock(myLock)) {
+	 *     // Critical section - lock is guaranteed to be acquired
+	 *     doSomethingCritical();
+	 * } // Lock 会自动释放
+	 * 
+ * + */ + public static LockStat tryLock(Lock lock, long timeout, TimeUnit timeUnit) { + boolean tryLock = false; + try { + tryLock = lock.tryLock(timeout, timeUnit); + } catch (InterruptedException e) { + tryLock = false; + } + return new LockStat(lock, tryLock); + } + + public static LockStat tryLock(Lock lock) { + boolean tryLock = lock.tryLock(); + return new LockStat(lock, tryLock); + } + + public static LockStat lock(Lock lock) { + lock.lock(); + return new LockStat(lock, true); + } + + public static class LockStat implements AutoCloseable { + private final Lock lock; + private final boolean isLocked; + + public LockStat(Lock lock, boolean isLocked) { + this.lock = lock; + this.isLocked = isLocked; + } + + public Lock getLock() { + return lock; + } + + public boolean isLocked() { + return isLocked; + } + + @Override + public void close() { + if (isLocked) { + lock.unlock(); + } + } + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/lock/LockUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/lock/LockUtilTest.java new file mode 100644 index 000000000..2b72798aa --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/thread/lock/LockUtilTest.java @@ -0,0 +1,228 @@ +package cn.hutool.core.thread.lock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LockUtil单元测试类 + */ +class LockUtilTest { + + private Lock testLock; + private ExecutorService executor; + + @BeforeEach + void setUp() { + testLock = new ReentrantLock(); + executor = Executors.newCachedThreadPool(); + } + + /** + * 测试tryLock方法 - 立即获取锁成功 + */ + @Test + void testTryLockSuccess() { + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock)) { + assertTrue(lockStat.isLocked(), "tryLock应该成功获取锁"); + assertSame(testLock, lockStat.getLock(), "返回的锁应该是传入的锁对象"); + } + } + + /** + * 测试tryLock方法 - 立即获取锁失败 + */ + @Test + void testTryLockFail() throws InterruptedException, ExecutionException, TimeoutException { + CountDownLatch latch = new CountDownLatch(1); + CountDownLatch releaseLatch = new CountDownLatch(1); + + // 在另一个线程中先获取锁 + Future future = executor.submit(() -> { + testLock.lock(); + latch.countDown(); // 通知已获取锁 + try { + releaseLatch.await(); // 等待释放信号 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + testLock.unlock(); + } + return null; + }); + + // 等待另一个线程获取锁 + latch.await(); + + // 尝试获取锁应该失败 + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock)) { + assertFalse(lockStat.isLocked(), "tryLock应该获取锁失败"); + assertSame(testLock, lockStat.getLock(), "返回的锁应该是传入的锁对象"); + } + + // 释放第一个线程的锁 + releaseLatch.countDown(); + future.get(1, TimeUnit.SECONDS); + } + + /** + * 测试tryLock方法 - 带超时时间成功获取锁 + */ + @Test + void testTryLockWithTimeoutSuccess() { + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock, 1, TimeUnit.SECONDS)) { + assertTrue(lockStat.isLocked(), "tryLock应该在超时时间内成功获取锁"); + assertSame(testLock, lockStat.getLock(), "返回的锁应该是传入的锁对象"); + } + + } + + /** + * 测试tryLock方法 - 带超时时间获取锁超时 + */ + @Test + void testTryLockWithTimeoutFail() throws InterruptedException, ExecutionException, TimeoutException { + CountDownLatch latch = new CountDownLatch(1); + CountDownLatch releaseLatch = new CountDownLatch(1); + + // 在另一个线程中先获取锁并持有较长时间 + Future future = executor.submit(() -> { + testLock.lock(); + latch.countDown(); // 通知已获取锁 + try { + releaseLatch.await(2, TimeUnit.SECONDS); // 持有锁2秒 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + testLock.unlock(); + } + return null; + }); + + // 等待另一个线程获取锁 + latch.await(); + + // 尝试在短时间内获取锁应该超时失败 + long startTime = System.currentTimeMillis(); + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock, 100, TimeUnit.MILLISECONDS)) { + assertFalse(lockStat.isLocked(), "tryLock应该因超时而获取锁失败"); + assertSame(testLock, lockStat.getLock(), "返回的锁应该是传入的锁对象"); + } + long elapsedTime = System.currentTimeMillis() - startTime; + assertTrue(elapsedTime >= 100 && elapsedTime < 500, "应该在大约100毫秒后超时"); + + // 释放第一个线程的锁 + releaseLatch.countDown(); + future.get(3, TimeUnit.SECONDS); + } + + /** + * 测试tryLock方法 - 处理InterruptedException + */ + @Test + void testTryLockInterrupted() throws InterruptedException { + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch lockAcquiredLatch = new CountDownLatch(1); + CountDownLatch testCompleteLatch = new CountDownLatch(1); + + // 先在主线程获取锁 + testLock.lock(); + + Future future = executor.submit(() -> { + startLatch.countDown(); + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock, 5, TimeUnit.SECONDS)) { + return lockStat.isLocked(); + } finally { + testCompleteLatch.countDown(); + } + }); + + // 等待子线程开始执行 + startLatch.await(); + Thread.sleep(100); // 确保子线程已开始等待锁 + + // 中断子线程 + future.cancel(true); + + // 释放锁 + testLock.unlock(); + + // 等待子线程完成 + testCompleteLatch.await(1, TimeUnit.SECONDS); + + try { + assertTrue(future.isCancelled() || !future.get(100, TimeUnit.MILLISECONDS), + "被中断的线程应该获取锁失败"); + } catch (ExecutionException | TimeoutException e) { + // 预期的异常,因为线程被中断 + assertTrue(future.isCancelled(), "线程应该被取消"); + } + } + + /** + * 测试lock方法 - 成功获取锁 + */ + @Test + void testLockSuccess() { + try (LockUtil.LockStat lockStat = LockUtil.lock(testLock)) { + assertTrue(lockStat.isLocked(), "lock应该成功获取锁"); + assertSame(testLock, lockStat.getLock(), "返回的锁应该是传入的锁对象"); + } + + } + + + /** + * 测试LockStat的close方法只在获取锁成功时才释放锁 + */ + @Test + void testLockStatCloseOnlyWhenLocked() { + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock)) { + assertTrue(lockStat.isLocked(), "重入锁获取锁成功"); + } // close方法不应该调用unlock,因为没有获取到锁 + + + // 测试获取锁成功的情况 + try (LockUtil.LockStat lockStat = LockUtil.tryLock(testLock)) { + assertTrue(lockStat.isLocked(), "应该获取锁成功"); + } // close方法应该调用unlock + + } + + /** + * 测试多次close调用的安全性 + */ + @Test + void testMultipleClose() { + LockUtil.LockStat lockStat = LockUtil.lock(testLock); + assertTrue(lockStat.isLocked(), "应该获取锁成功"); + + } + + /** + * 测试嵌套使用try-with-resources + */ + @Test + void testNestedTryWithResources() { + ReentrantLock reentrantLock = new ReentrantLock(); + + try (LockUtil.LockStat outerLockStat = LockUtil.lock(reentrantLock)) { + assertTrue(outerLockStat.isLocked(), "外层应该获取锁成功"); + + // 由于ReentrantLock支持重入,内层也应该成功 + try (LockUtil.LockStat innerLockStat = LockUtil.lock(reentrantLock)) { + assertTrue(innerLockStat.isLocked(), "内层应该获取锁成功(重入)"); + } + + // 外层锁仍应该有效 + assertTrue(outerLockStat.isLocked(), "外层锁仍应该有效"); + } + + } + +}