制作和测试一把锁
制作锁
编写分布式锁的注意点
- 注意锁的可重试、可续期、以及加锁和释放锁的一致
- 如果连接不上redis如何处理?如果线程抢占锁失败如何处理?
加锁业务逻辑
- 包含业务要求的重试、以及redis连接不上、线程抢占锁失败的处理
1 | public static <T, R> R runWithLock(Function<T, R> function, T req, Class<? extends AssuranceSyncHandler<T,R>> clazz, |
内部锁加锁逻辑
- 内部锁的粒度要控制,以及如果两个锁释放失败如何处理
- 感觉也没必要加内部锁,因为如果并发度不高的话,每次都会获取到redis锁,再加上内部锁。反而降低了性能。如果单机并发很高,可以考虑加内部锁
1 | private static boolean tryLock(ZedBody zedBody, AssuranceSyncHandler<?,?> zedSynchronizedHandler) { |
核心加锁逻辑
- 包含锁的可重入处理
1 | public Boolean lock(ZedBody zedBody) { |
Redis连接逻辑
- 包含redis连接不上的重试
1 | public boolean set(String key, String value, int expireTimeSec) { |
测试锁
基于上面的分布式锁,当我想在单机上通过UT来保证锁的正确性,该如何写UT呢?
第一版
我写的第一版测试代码如下:
1 |
|
眼尖的同学可能一眼就看出来了程序的问题,主线程无法感知到测试结果。如果要保证主线程感知到测试结果,有两个需要注意的点,第一就是需要把测试线程的值传递给主线程。第二个点就是要保证主线程比测试线程执行的晚。常见的方式有 CountDownLatch、BlockQueue和共享内存等
第二版
所以我尝试写了第二版测试代码:
1 |
|
执行这个测试程序后,会有下面几个异常的case:
- 31行的断言失败,firstThreadAns.get() == null
原因是因为14行,测试线程提前执行latch.countDown(),导致测试线程没有把测试结果传递给主线程,主线程就开始check结果 - 主线程一直等待,导致程序不能结束
原因是因为第二个线程先执行,第一个线程没有获得到锁,抛出加锁失败异常,导致没有执行latch.countDown()方法,进行会导致主线程一直pending。解决方案很简单,就是保证第一个测试线程先于第二个测试线程执行。可以使用Semaphore、CountDownLatch和锁,但是这里要注意,不能用Thread.join()
第三版
1 |
|

完美