前言
看了很多用
redisson
實(shí)現(xiàn)分布式鎖的博客, 對(duì)他們使用的方式我個(gè)人認(rèn)為有一點(diǎn)點(diǎn)自己的看法, 接下來(lái)本文將以例子來(lái)驗(yàn)證為什么會(huì)有誤解, 和看看正確的方式應(yīng)該怎么寫?
本文源代碼: 源代碼下載
大多數(shù)認(rèn)為的寫法
看到很多人都是這樣寫
RLock lock = redisson.getLock(KEY);
lock.lock()
// do your own work
lock.unlock()
簡(jiǎn)單看完源代碼后, 我看到該方法會(huì)去調(diào)用一個(gè)響應(yīng)一個(gè)中斷的
lockInterruptibly
,此時(shí)我就有點(diǎn)疑惑了, 響應(yīng)中斷就是表示線程如果發(fā)生中斷就不會(huì)在等待隊(duì)列中等待(當(dāng)然redisson
是采用SUB/PUB
的方式),(本文不分析源碼哈,對(duì)該鎖的源碼分析會(huì)放到專門博客里面分析, 主要是驗(yàn)證該如何使用)可以看下圖:
上圖中
lock
等方法會(huì)最終調(diào)用public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException
該方法會(huì)拋出異常, 然而lock
方法并沒有把這個(gè)異常拋出給使用者, 而是采用捕獲異常,并且重新設(shè)置中斷狀態(tài).
這下就有點(diǎn)明白了, 是不是需要用戶自己來(lái)判斷當(dāng)前線程的狀態(tài)來(lái)判斷當(dāng)前線程是否獲得鎖了呢?已經(jīng)猜到這一步了, 接下來(lái)就需要驗(yàn)證一下自己的猜想
例子1:驗(yàn)證上面的寫法
我是用
maven
項(xiàng)目構(gòu)建的一個(gè)小項(xiàng)目,因此加入如下依賴
<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充分運(yùn)行
thread_2.start();
TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會(huì)不會(huì)拿到鎖
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.");
}
}
}
在該例子中我啟動(dòng)了兩個(gè)線程分別為
thread-1
和thread-2
, 并且讓線程thread-1
獲得鎖后休息1分鐘, 線程thread-2
在等待鎖的過程中用主線程中斷線程thread-2
以此達(dá)到測(cè)試的目的.
接下來(lái)就需要觀察結(jié)果, 在線程
thread-2
中斷的時(shí)候會(huì)不會(huì)獲得鎖, 如何觀察呢? 因?yàn)槲覀冎廊绻粋€(gè)線程嘗試去釋放一個(gè)屬于別的線程的鎖的時(shí)候, 會(huì)拋出一個(gè)運(yùn)行時(shí)異常叫做異常
, 另外我們也可以通過觀察redis
里面數(shù)據(jù)的變化情況來(lái)判斷thread-2
到底有沒有獲得鎖.
運(yùn)行結(jié)果:
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
還沒有結(jié)束的時(shí)候,也就是在thread-1
在獲得鎖但是還沒有釋放鎖的時(shí)候,thread-2
由于被別的線程中斷停止了等待從lock.lock(10, TimeUnit.MINUTES)
的阻塞狀態(tài)中返回繼續(xù)執(zhí)行接下來(lái)的邏輯,并且由于嘗試去釋放一個(gè)屬于線程thread-1
的鎖而拋出了一個(gè)運(yùn)行時(shí)異常導(dǎo)致該線程thread-2
結(jié)束了, 然而thread-2
完成了一系列操作后,線程thread-1
才釋放了自己的鎖. 所以thread-2
并沒有獲得鎖,卻執(zhí)行了需要同步的內(nèi)容,還嘗試去釋放鎖.
從redis的角度看線程thread-2有沒有獲得鎖: 下圖便是整個(gè)運(yùn)行期間
KEY
中內(nèi)容的變化,從始至終redis
中的testlock
的key
只產(chǎn)生了9f178836-f7e1-44fe-a89d-2db52f399c0d:20
這一個(gè)key
,很明顯這個(gè)key是屬于線程thread-1
的,因?yàn)?code>thread-1先獲得了鎖.如果thread-2
獲得了線程, 在key9f178836-f7e1-44fe-a89d-2db52f399c0d:20
消失后應(yīng)該產(chǎn)生一個(gè)屬于線程thread-2
的key.
總結(jié): 總上面兩種角度的分析來(lái)看,
thread-2
在被別的線程中斷后并沒有獲得鎖, 所以這種寫法不嚴(yán)謹(jǐn)!
例子2: 嚴(yán)謹(jǐn)?shù)膶懛?/h1>
看了例子1, 現(xiàn)在已經(jīng)驗(yàn)證了我們的想法是對(duì)的, 在線程發(fā)生中斷的時(shí)候該線程會(huì)立馬從阻塞狀態(tài)中返回, 并且沒有獲得鎖. 因此我們看看第二個(gè)例子看看如何寫會(huì)比較嚴(yán)謹(jǐn).
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充分運(yùn)行
thread_2.start();
TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會(huì)不會(huì)拿到鎖
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.");
}
}
}
結(jié)果如下: 符合預(yù)期, 沒有報(bào)異常, 線程都是正常退出.
thread-1 gets lock.
thread-2 does not get lock.
thread-2 ends.
thread-1 ends.
看了例子1, 現(xiàn)在已經(jīng)驗(yàn)證了我們的想法是對(duì)的, 在線程發(fā)生中斷的時(shí)候該線程會(huì)立馬從阻塞狀態(tài)中返回, 并且沒有獲得鎖. 因此我們看看第二個(gè)例子看看如何寫會(huì)比較嚴(yán)謹(jǐn).
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充分運(yùn)行
thread_2.start();
TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會(huì)不會(huì)拿到鎖
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.");
}
}
}
結(jié)果如下: 符合預(yù)期, 沒有報(bào)異常, 線程都是正常退出.
thread-1 gets lock.
thread-2 does not get lock.
thread-2 ends.
thread-1 ends.