订阅锁释放事件,并阻塞等待锁释放,有效的解决了无效的锁申请浪费资源的问题:
基于信号量,当锁被其它资源占用时,当前线程通过 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}对象(线程B),并进而调用{@link AsyncSemaphore#acquire}方法此时能够成功获取信号量,并执行{@code listener.run}方法。在上述执行{@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
。
继续调用semaphore
的{@link AsyncSemaphore#release()}方法,以此类推。。。重复上述步骤(1)
代码如下:
@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通知完成。
之后会通过如下方式阻塞等待订阅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
逻辑。