前言
看了很多用
redisson
实现分布式锁的博客, 对他们使用的方式我个人认为有一点点自己的看法, 接下来本文将以例子来验证为什么会有误解, 和看看正确的方式应该怎么写?
本文源代码: 源代码下载
大多数认为的写法
看到很多人都是这样写
RLock lock = redisson.getLock(KEY);
lock.lock()
// do your own work
lock.unlock()
简单看完源代码后, 我看到该方法会去调用一个响应一个中断的
lockInterruptibly
,此时我就有点疑惑了, 响应中断就是表示线程如果发生中断就不会在等待队列中等待(当然redisson
是采用SUB/PUB
的方式),(本文不分析源码哈,对该锁的源码分析会放到专门博客里面分析, 主要是验证该如何使用)可以看下图:
上图中
lock
等方法会最终调用public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException
该方法会抛出异常, 然而lock
方法并没有把这个异常抛出给使用者, 而是采用捕获异常,并且重新设置中断状态.
这下就有点明白了, 是不是需要用户自己来判断当前线程的状态来判断当前线程是否获得锁了呢?已经猜到这一步了, 接下来就需要验证一下自己的猜想
例子1:验证上面的写法
我是用
maven
项目构建的一个小项目,因此加入如下依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
加入以下例子.
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TestDistributedRedisLock {
private static CountDownLatch finish = new CountDownLatch(2);
private static final String KEY = "testlock";
private static Config config;
private static Redisson redisson;
static {
config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379");
redisson = (Redisson)Redisson.create(config);
}
public static void main(String[] args) {
Thread thread_1 = new LockWithoutBoolean("thread-1");
Thread thread_2 = new LockWithoutBoolean("thread-2");
thread_1.start();
try {
TimeUnit.SECONDS.sleep(10); // 睡10秒钟 为了让thread_1充分运行
thread_2.start();
TimeUnit.SECONDS.sleep(10); // 让thread_2 等待锁
thread_2.interrupt(); // 中断正在等待锁的thread_2 观察thread_2是否会不会拿到锁
finish.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
redisson.shutdown();
}
}
static class LockWithoutBoolean extends Thread {
private String name;
public LockWithoutBoolean(String name) {
super(name);
}
public void run() {
RLock lock = redisson.getLock(KEY);
lock.lock(10, TimeUnit.MINUTES);
System.out.println(Thread.currentThread().getName() + " gets lock. and interrupt: " + Thread.currentThread().isInterrupted());
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
try {
lock.unlock();
} finally {
finish.countDown();
}
}
System.out.println(Thread.currentThread().getName() + " ends.");
}
}
}
在该例子中我启动了两个线程分别为
thread-1
和thread-2
, 并且让线程thread-1
获得锁后休息1分钟, 线程thread-2
在等待锁的过程中用主线程中断线程thread-2
以此达到测试的目的.
接下来就需要观察结果, 在线程
thread-2
中断的时候会不会获得锁, 如何观察呢? 因为我们知道如果一个线程尝试去释放一个属于别的线程的锁的时候, 会抛出一个运行时异常叫做异常
, 另外我们也可以通过观察redis
里面数据的变化情况来判断thread-2
到底有没有获得锁.
运行结果:
thread-1 gets lock. and interrupt: false
thread-2 gets lock. and interrupt: true
Exception in thread "thread-2" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 21
at org.redisson.RedissonLock.unlock(RedissonLock.java:353)
at com.example.TestDistributedRedisLock$LockWithoutBoolean.run(TestDistributedRedisLock.java:53)
thread-1 ends.
从程序的角度看线程thread-2有没有获得锁: 可以看到在
thread-1
还没有结束的时候,也就是在thread-1
在获得锁但是还没有释放锁的时候,thread-2
由于被别的线程中断停止了等待从lock.lock(10, TimeUnit.MINUTES)
的阻塞状态中返回继续执行接下来的逻辑,并且由于尝试去释放一个属于线程thread-1
的锁而抛出了一个运行时异常导致该线程thread-2
结束了, 然而thread-2
完成了一系列操作后,线程thread-1
才释放了自己的锁. 所以thread-2
并没有获得锁,却执行了需要同步的内容,还尝试去释放锁.
从redis的角度看线程thread-2有没有获得锁: 下图便是整个运行期间
KEY
中内容的变化,从始至终redis
中的testlock
的key
只产生了9f178836-f7e1-44fe-a89d-2db52f399c0d:20
这一个key
,很明显这个key是属于线程thread-1
的,因为thread-1
先获得了锁.如果thread-2
获得了线程, 在key9f178836-f7e1-44fe-a89d-2db52f399c0d:20
消失后应该产生一个属于线程thread-2
的key.
总结: 总上面两种角度的分析来看,
thread-2
在被别的线程中断后并没有获得锁, 所以这种写法不严谨!
例子2: 严谨的写法
看了例子1, 现在已经验证了我们的想法是对的, 在线程发生中断的时候该线程会立马从阻塞状态中返回, 并且没有获得锁. 因此我们看看第二个例子看看如何写会比较严谨.
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TestDistributedRedisLockWithBool {
private static CountDownLatch finish = new CountDownLatch(2);
private static final String KEY = "testlock";
private static Config config;
private static Redisson redisson;
static {
config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379");
redisson = (Redisson)Redisson.create(config);
}
public static void main(String[] args) {
Thread thread_1 = new LockWithBoolean("thread-1");
Thread thread_2 = new LockWithBoolean("thread-2");
thread_1.start();
try {
TimeUnit.SECONDS.sleep(10); // 睡10秒钟 为了让thread_1充分运行
thread_2.start();
TimeUnit.SECONDS.sleep(10); // 让thread_2 等待锁
thread_2.interrupt(); // 中断正在等待锁的thread_2 观察thread_2是否会不会拿到锁
finish.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
redisson.shutdown();
}
}
static class LockWithBoolean extends Thread {
private String name;
public LockWithBoolean(String name) {
super(name);
}
public void run() {
RLock lock = redisson.getLock(KEY);
lock.lock(10, TimeUnit.MINUTES);
if (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " gets lock.");
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " does not get lock.");
}
System.out.println(Thread.currentThread().getName() + " ends.");
}
}
}
结果如下: 符合预期, 没有报异常, 线程都是正常退出.
thread-1 gets lock.
thread-2 does not get lock.
thread-2 ends.
thread-1 ends.