Redisson簡介
Javaer都知道Jedis,Jedis是Redis的Java實現的客戶端,其API提供了比較全面的Redis命令的支持。Redission也是Redis的客戶端,相比於Jedis功能簡單。Jedis簡單使用阻塞的I/O和redis交互,Redission通過Netty支持非阻塞I/O。Jedis最新版本2.9.0是2016年的快3年了沒有更新,而Redission最新版本是2018.10月更新。
Redission封裝了鎖的實現,其繼承了java.util.concurrent.locks.Lock的接口,讓我們像操作我們的本地Lock一樣去操作Redission的Lock。
下面直接上乾貨
使用樣例
@GetMapping("/testLock")
public String lock(){
RLock lock = redissonClient.getLock("anyLock");
lock.lock();
try {
System.out.println(lock);
Thread.sleep(TimeUnit.SECONDS.toMillis(30));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
return "ok" ;
}
源碼分析
- 獲取鎖
調用getLock()方法後實際返回一個RedissonLock對象
- 加鎖
在RedissonLock對象的lock()方法主要調用tryAcquire()方法,由於leaseTime == -1,於是走tryLockInnerAsync()方法
- 加鎖細節
結合上面的參數聲明,我們可以知道,這裏KEYS[1]就是getName(),ARGV[2]是getLockName(threadId),假設前面獲取鎖時傳的name是“anyLock”,假設調用的線程ID是Thread-1,假設成員變量UUID類型的id是85b196ce-e6f2-42ff-b3d7-6615b6748b5d:65那麼KEYS[1]=anyLock,ARGV[2]=85b196ce-e6f2-42ff-b3d7-6615b6748b5d:Thread-1 ,因此,這段腳本的意思是1、判斷有沒有一個叫“anyLock”的key2、如果沒有,則在其下設置一個字段爲“85b196ce-e6f2-42ff-b3d7-6615b6748b5d:Thread-1”,值爲“1”的鍵值對 ,並設置它的過期時間3、如果存在,則進一步判斷“85b196ce-e6f2-42ff-b3d7-6615b6748b5d:Thread-1”是否存在,若存在,則其值加1,並重新設置過期時間4、返回“anyLock”的生存時間(毫秒)
- 加鎖redis結構
這裏用的數據結構是hash,hash的結構是: key 字段1 值1 字段2 值2 。。。用在鎖這個場景下,key就表示鎖的名稱,也可以理解爲臨界資源,字段就表示當前獲得鎖的線程所有競爭這把鎖的線程都要判斷在這個key下有沒有自己線程的字段,如果沒有則不能獲得鎖,如果有,則相當於重入,字段值加1(次數)
- 解鎖
我們還是假設name=anyLock,假設線程ID是Thread-1,同理,我們可以知道KEYS[1]是getName(),即KEYS[1]=anyLock,KEYS[2]是getChannelName(),即KEYS[2]=redisson_lock__channel:{anyLock},ARGV[1]是LockPubSub.unlockMessage,即ARGV[1]=0,ARGV[2]是生存時間,ARGV[3]是getLockName(threadId),即ARGV[3]=85b196ce-e6f2-42ff-b3d7-6615b6748b5d:Thread-1,因此,上面腳本的意思是:1、判斷是否存在一個叫“anyLock”的key2、如果不存在,向Channel中廣播一條消息,廣播的內容是0,並返回1。3、如果存在,進一步判斷字段85b196ce-e6f2-42ff-b3d7-6615b6748b5d:Thread-1是否存在。4、若字段不存在,返回空,若字段存在,則字段值減1,5、若減完以後,字段值仍大於0,則返回0。6、減完後,若字段值小於或等於0,則廣播一條消息,廣播內容是0,並返回1;可以猜測,廣播0表示資源可用,即通知那些等待獲取鎖的線程現在可以獲得鎖了
- 等待
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return;
}
RFuture<RedissonLockEntry> future = subscribe(threadId);
if (interruptibly) {
commandExecutor.syncSubscriptionInterrupted(future);
} else {
commandExecutor.syncSubscription(future);
}
try {
while (true) {
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
if (ttl >= 0) {
try {
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
future.getNow().getLatch().acquire();
} else {
future.getNow().getLatch().acquireUninterruptibly();
}
}
}
} finally {
unsubscribe(future, threadId);
}
// get(lockAsync(leaseTime, unit));
}
這裏會訂閱Channel,當資源可用時可以及時知道,並搶佔,防止無效的輪詢而浪費資源當資源可用用的時候,循環去嘗試獲取鎖,由於多個線程同時去競爭資源,所以這裏用了信號量,對於同一個資源只允許一個線程獲得鎖,其它的線程阻塞
- 總結
關注Github:1/2極客
關注博客:御前提筆小書童
關注網站:HuMingfeng
關注公衆號:開發者的花花世界