Java实现一个等待异步返回的阻塞方法(便于同步方法等待异步返回结果)
此方法同样兼容jdk21的虚拟线程。
需要Guava库,或可利用其他缓存库代替:
@Slf4j
@NoArgsConstructor
public class AsyncResultLock<K> {
private final Cache<K, Thread> lockMap = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.concurrencyLevel(Runtime.getRuntime().availableProcessors() + 1)
.weakValues()
.build();
private final Cache<K, Object> cacheMap = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.concurrencyLevel(Runtime.getRuntime().availableProcessors() + 1)
.build();
public <T> T park(K key, long timeout, TimeUnit unit, Callable<T> before, Consumer<T> after) throws Exception {
lockMap.put(key, Thread.currentThread());
T ret;
try {
ret = before.call();
} catch (Exception e) {
lockMap.invalidate(key);
throw e;
}
LockSupport.parkNanos(TimeUnit.NANOSECONDS.convert(timeout, unit));
@SuppressWarnings("unchecked")
T cache = (T) cacheMap.getIfPresent(key);
after.accept(cache);
lockMap.invalidate(key);
return ret;
}
public boolean unPark(K key, Object result) {
Thread thread = lockMap.getIfPresent(key);
if (thread != null) {
cacheMap.put(key, result);
}
LockSupport.unpark(thread);
return thread != null;
}
}
使用方法:
阻塞方法:
@SneakyThrows
public void sendAndWait(String cameraId, MessageQo message, long timeout, TimeUnit unit, Consumer<MessageResult> consumer) {
Channel ch = connectionPool.borrow(cameraId);
if (message.getId() == null && ch.attr(ChannelAttr.COUNTER.getAttributeKey()).get() instanceof CircularCounter c) {
int id = c.getAndIncrement();
message.setId(id);
}
ASYNC_RESULT_LOCK.park(message.getId(), timeout, unit, () -> {
try {
String string = VZConstants.OBJECT_MAPPER.writeValueAsString(message);
log.info("发送消息:->{} {}", cameraId, string);
DataPacket dataPacket = new DataPacket((byte) 0, Unpooled.copiedBuffer(string, VZConstants.CHARSET));
ch.writeAndFlush(dataPacket);
} finally {
connectionPool.revert(cameraId, ch);
}
return null;
}, consumer);
}
异步响应得到结果时:
private void consume(String key, VZCommand command, MessageResult result) {
ASYNC_RESULT_LOCK.unPark(result.getId(), result);
}