详解Java中异步转同步的六种方法

网友投稿 959 2022-07-23


目录一、问题应用场景二、分析三、实现方法1.轮询与休眠重试机制2.wait/notify3.Lock Condition4.CountDownLatch5.CyclicBarrier6.LockSupport

一、问题

应用场景

应用中通过框架发送异步命令时,不能立刻返回命令的执行结果,而是异步返回命令的执行结果。

那么,问题来了,针对应用中这种异步调用,能不能像同步调用一样立刻获取到命令的执行结果,如何实现异步转同步?

二、分析

首先,解释下同步和异步

同步,就是发出一个调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。异步,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。

对于异步调用,调用的返回并不受调用者控制。

异步转同步主要实现思路:所有实现原理类似,是在发出调用的线程中进行阻塞等待结果,调用完成后通过回调、设置共享状态或通知进行阻塞状态的解除,继续执行后续操作。

三、实现方法

通常,实现中,不会无限的等待,一般会设定一个超时时间,具体超时时间根据具体场景确定。

下面以回调的方式介绍几种常用实现异步转同步的方法:

1.轮询与休眠重试机制

采用轮询与休眠重试机制,线程将反复在休眠和测试状态条件中之间切换,直到超时或者状态条件满足继续向下执行。这种方式,超时时间控制不准确,sleep时间需要在响应性和CPU使用率之间进行权衡。

private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待时间 5分钟

private final Object lock = new Object();

//3.结果返回后进行回调,解除阻塞

@Override

public void callback(AsynResponse response){

synchronized(lock){

//设置状态条件

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;

long remaining = MILLIS_OF_WAIT_TIME;//剩余等待时间

while(remaining > 0){

synchronized(lock){

if(状态条件未满足){

remaining = future - System.currentTimeMillis();

Thread.sleep(时间具体场景确定);

}

}

````}

//4.超时或结果正确返回,对结果进行处理

return result;

}

2.wait/notify

任意一个java对象,都拥有一组监视器方法(wait、notify、notifyAll等方法),这些方法和synchronized同步关键字配合,可以实现等待/通知模式。但是使用wait/notify,使线程的阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程是很困难的,所以是要么随机唤醒等待在条件队列上一个线程(notify),要么唤醒所有的(notifyAll,但是很低效)。当多个线程基于不同条件在同一条件队列上等待时,如果使用notify而不是notifyAll,很容易导致信号丢失的问题,所以必须谨慎使用wait/notify方法。

private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待时间 5分钟

private final Object lock = new Object();

//3.结果返回后进行回调,解除阻塞

@Override

public void callback(AsynResponse response){

synchronized(lock){

lock.notifyAll();

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;

long remaining = MILLIS_OF_WAIT_TIME;//剩余等待时间

synchronized(lock){

while(条件未满足 && remaining > 0){ //被通知后要检查条件

lock.wait(remaining);

remaining = future - System.currentTimeMillis();

}

````}

//4.超时或结果正确返回,对结果进行处理

return result;

}

3.Lock Condition

使用Lock的Condition队列的实现方式和wait/notify方式类似,但是Lock支持多个Condition队列,并且支持等待状态中响应中断。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待时间 5分钟

private final Lock lock = new ReentrantLock();

private final Condition condition = lock.newCondition();

//3.结果返回后进行回调,解除阻塞

@Override

public void callbaLPxnLPSck(AsynResponse response){

lock.lock();//这是前提

try {

condition.signal();

}finally {

lock.unlock();

}

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

lock.lock();//这是前提

try {

condition.await();

} catch (InterruptedException e) {

//TODO

}finally {

lock.unlock();

}

//4.超时或结果正确返回,对结果进行处理

return result;

}

4.CountDownLatch

使用CountDownLatch可以实现异步转同步,它好比计数器,在创建实例CountDownLatch对象的时候传入数字,每使用一次 countDown() 方法计数减1,当数字减到0时, await()方法后的代码将可以执行,未到0之前将一直阻塞等待。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待时间 5分钟

private final CountDownLatch countDownLatch = new CountDownLatch(1);

//3.结果返回后进行回调,解除阻塞

@Override

public void callback(AsynResponse response){

countDownLatch.countDown();

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

try {

countDownLatch.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);

} catch (InterruptedException e) {

//TODO

}

//4.超时或结果正确返回,对结果进行处理

return result;

}

5.CyclicBarrier

让一组线程达到一个屏障(也可以叫同步点)时被阻塞,直到等待最后一个线程到达屏障时,屏障才开门,所有被屏障拦截的线程才会继续执行。

每个线程通过调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前的的线程被阻塞。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待时间 5分钟

private final CountDownLatch cyclicBarrier= new CyclicBarrier(2);//设置屏障拦截的线程数为2

//3.结果返回后进行回调,解除阻塞

@Override

public void callback(AsynResponse response){

//我也到达屏障了,可以开门了

cyclicBarrier.await();

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

try {

//我到达屏障了,还没开门,要等一等

cyclicBarrier.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);

} catch (InterruptedException e) {

//TODO

}

//4.超时或结果正确返回,对结果进行处理

return result;

}

CountDownLatch和CyclicBarrier实现类似,区别是CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset重置,

所以CyclicBarrier能处理更为复杂的业务场景。在异步转同步中,计数器不会重用,所以使用CountDownLatch实现更适合。

6.LockSupport

LockSupport定义了一组公共静态方法,提供了最基本的线程阻塞和唤醒的方法。

private static long NANOS_OF_WAIT_TIME = 300000000L;// 等待时间 5分钟

private final LockSupport lockSupport = new LockSupport();

//3.结果返回后进行回调,解除阻塞

@Override

public void callback(AsynResponse response){

lockSupport.unpark();

}

public Result getResult() throws ErrorCodeException {

// 1.异步调用

// 2.阻塞等待异步响应

try {

lockSupport.parkNanos(NANOS_OF_WAIT_TIME);

} catch (InterruptedException e) {

//TODO

}

//4.超时或结果正确返回,对结果进行处理

return result;

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Cloud Alibaba使用Nacos作为注册中心和配置中心
下一篇:Spring Cloud Alibaba微服务组件Sentinel实现熔断限流
相关文章

 发表评论

暂时没有评论,来抢沙发吧~