RedissonLock#subscribe
訂閱鎖釋放事件,並阻塞等待鎖釋放,有效的解決了無效的鎖申請浪費資源的問題:
基於信號量,當鎖被其它資源佔用時,當前線程通過 Redis 的 channel 訂閱鎖的釋放事件,一旦鎖釋放會發消息通知待等待的線程進行競爭.
1、當 this.await 返回 false,說明等待時間已經超出獲取鎖最大等待時間,取消訂閱並返回獲取鎖失敗.
2、當 this.await 返回 true,進入循環嘗試獲取鎖.
protected final LockPubSub pubSub;
...
protected RFuture<RedissonLockEntry> subscribe(long threadId) {
return pubSub.subscribe(getEntryName(), getChannelName());
}
entryName 格式:“id:name”;
channelName 格式:“redisson_lock__channel:{name}”;
RedissonLock#pubSub
是在RedissonLock構造函數中通過如下方式初始化的:
this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
而subscribeService
在MasterSlaveConnectionManager
的實現中又是通過如下方式構造的,其中this
就是MasterSlaveConnectionManager
實例,config
則爲MasterSlaveServersConfig
實例:
subscribeService = new PublishSubscribeService(this, config);
//PublishSubscribeService.java
private final LockPubSub lockPubSub = new LockPubSub(this);
private final AsyncSemaphore[] locks = new AsyncSemaphore[50];
public PublishSubscribeService(ConnectionManager connectionManager, MasterSlaveServersConfig config) {
super();
this.connectionManager = connectionManager;
this.config = config;
for (int i = 0; i < locks.length; i++) {
locks[i] = new AsyncSemaphore(1);
}
}
我們會發現其在初始化時,會初始化一組信號量,至於用途是什麼,我們會在後面揭曉。現在我們知道RedissonLock#pubSub
是怎麼初始化的了,讓我們回到訂閱流程。
/*
* @param entryName 格式:“id:name”
* @param channelName 格式:“redisson_lock__channel:{name}”
* @return
*/
//PublishSubscribe.java
public RFuture<E> subscribe(String entryName, String channelName) {
//代碼@1 對於同一個鎖,semaphore爲單例
AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
//每一個嘗試獲取鎖失敗的線程都會創建一個listenerHolder和一個newPromise,所以這裏的listenerHolder、newPromise是與當前獲取鎖的線程綁定的
AtomicReference<Runnable> listenerHolder = new AtomicReference<>();
RPromise<E> newPromise = new RedissonPromise<E>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return semaphore.remove(listenerHolder.get());
}
};
//每一個嘗試獲取鎖失敗的線程都會創建一個listener
Runnable listener = () -> {
// entry's type is RedissonLockEntry
E entry = entries.get(entryName);
if (entry != null) {
entry.acquire();
semaphore.release();
entry.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
// new RedissonLockEntry
E value = createEntry(newPromise);
value.acquire();
E oldValue = entries.putIfAbsent(entryName, value);
if (oldValue != null) {
oldValue.acquire();
semaphore.release();
oldValue.getPromise().onComplete(new TransferListener<E>(newPromise));
return;
}
RedisPubSubListener<Object> redisPubSubListener = createListener(channelName, value);
service.subscribe(LongCodec.INSTANCE, channelName, semaphore, redisPubSubListener);
};
//首個嘗試獲取鎖失敗的線程acquire操作不會阻塞,會直接觸發執行listener.run方法
semaphore.acquire(listener);
listenerHolder.set(listener);
return newPromise;
}
代碼@1
public AsyncSemaphore getSemaphore(ChannelName channelName) {
return locks[Math.abs(channelName.hashCode() % locks.length)];
}
通過入參channelName
的格式redisson_lock__channel:{name}
我們知道,對於同一個鎖,這裏獲取的信號量是同一個。
代碼@2
public void acquire(Runnable listener) {
acquire(listener, 1);
}
public void acquire(Runnable listener, int permits) {
synchronized (this) {
if (counter < permits) {
listeners.add(new Entry(listener, permits));
return;
} else {
counter -= permits;
}
}
listener.run();
}
AsyncSemaphore#counter
代表當前信號量允許的請求數,初始值爲1,所以對於首個嘗試獲取該鎖失敗的線程會直接觸發執行listener.run
方法。而對於後續嘗試獲取該鎖失敗的線程則會創建Entry
對象(保存listener
與permits
的映射關係)並保存到AsyncSemaphore#listeners
。
嘗試獲取鎖失敗的線程會走到此流程訂閱redis通知。
-
假設有A、B、C、etc多個線程順序調用{@link #subscribe}方法,因爲semaphore初始信號量爲1(參見{@link PublishSubscribeService#PublishSubscribeService}方法),所以線程A可以獲得信號量(參見{@link AsyncSemaphore#acquire}方法),並執行{@code listener.run}方法,而對於之後的線程B、C、etc,其listener會被封裝成{@link AsyncSemaphore.Entry}保存在semaphore的{@link AsyncSemaphore#listeners}中。
-
首個嘗試獲取鎖失敗的線程{@code semaphore.acquire(listener);}操作不會阻塞,會直接觸發執行{@code listener.run}方法。這裏線程A作爲首個嘗試獲取鎖失敗的線程,會執行{@code listener.run}方法,其發現{@link PublishSubscribe#entries}中並沒有當前鎖對應的記錄,會創建一個{@link RedissonLockEntry}(參見{@link #createEntry}方法)並添加到{@link PublishSubscribe#entries}(姑且記爲 A_e),key爲"id:name"。同時會註冊監聽器redisPubSubListener。
(1)
semaphore
的{@link AsyncSemaphore#release()}方法會被調用,從{@link AsyncSemaphore#listeners}中取出一個{@link AsyncSemaphore.Entry}對象,並進而調用{@link AsyncSemaphore#acquire}方法此時能夠成功獲取信號量,並執行{@code listener.run}方法,繼續調用semaphore
的{@link AsyncSemaphore#release()}方法,以此類推。。。在上述執行{@code listener.run}方法時,{@code E entry = entries.get(entryName);}獲取到的{@link RedissonLockEntry}對象是前面線程A寫入的A_e。接着調用{@code RedissonPromise#onComplete}方法爲線程A的
newPromise
添加監聽,監聽器保存在{@link RedissonPromise#promise}對象的{@link DefaultPromise#listeners}中。監聽器的作用用於在線程A的newPromise
({@link RedissonLockEntry#promise})完成時將其的結果同步到當前線程(線程B、C、etc)的newPromise
。代碼如下:@Override public void onComplete(BiConsumer<? super T, ? super Throwable> action) { promise.addListener(f -> { if (!f.isSuccess()) { action.accept(null, f.cause()); return; } action.accept((T) f.getNow(), null); }); }
(2) 監聽器的{@link BaseRedisPubSubListener#onStatus}方法被調用,標記A_e的{@link RedissonLockEntry#promise}也即線程A的
newPromise
完成(執行代碼:value.getPromise().trySuccess(value)
),其會喚醒註冊在newPromise
的{@link RedissonPromise#promise}對象的{@link DefaultPromise#listeners}中的所有監聽器,從而在線程A的newPromise
({@link RedissonLockEntry#promise})完成時將其的結果同步到當前線程(線程B、C、etc)的newPromise
。上述(1)(2)所達到的效果就是同步等待(參見 RedissonLock {@code subscribeFuture.await(time, TimeUnit.MILLISECONDS)})註冊redis通知完成。
-
redisPubSubListener監聽器在收到redis的通知時,對於{@link @channelName}的消息會調用{@link PublishSubscribe.onMessage}方法釋放一個信號量,喚醒等待的entry.getLatch().tryAcquire去再次嘗試申請鎖。
之後會通過如下方式阻塞等待訂閱redis通知完成。
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
unsubscribe(subscribeFuture, threadId);
}
});
}
acquireFailed(threadId);
return false;
}
如果等待超時,會通過RedissonPromise#cancel
方法取消當前線程的訂閱。而cancel
方法的實現如下:
RPromise<E> newPromise = new RedissonPromise<E>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return semaphore.remove(listenerHolder.get());
}
};
//AsyncSemaphore.java
public boolean remove(Runnable listener) {
synchronized (this) {
return listeners.remove(new Entry(listener, 0));
}
}
前面我們提到過,線程B、C、etc因爲獲取不到信號量,其listener會被封裝成{@link AsyncSemaphore.Entry}保存在semaphore
的{@link AsyncSemaphore#listeners}中。這裏就是各線程將各自的listener
從semaphore
的{@link AsyncSemaphore#listeners}中移除。對於第一個嘗試獲取鎖失敗的線程A,其並不會被保存在listeners
中,所以這裏移除會失敗,即RedissonPromise#cancel
方法會返回false。進而走到下面的subscribeFuture.onComplete
邏輯。