先来说一下对异步和同步的理解:

同步调用:调用方在调用过程中,持续等待返回结果。

异步调用:调用方在调用过程中,不直接等待返回结果,而是执行其他任务,结果返回形式通常为回调函数。

其实,两者的区别还是很明显的,这里也不再细说,我们主要来说一下Java如何将异步调用转为同步。换句话说,就是需要在异步调用过程中,持续阻塞至获得调用结果。

不卖关子,先列出五种方法,然后一一举例说明:

  • 使用wait和notify方法,synchronized
  • 使用条件锁ReentrantLock
  • Future
  • 使用CountDownLatch
  • 使用CyclicBarrier

0.构造一个异步调用

首先,写demo需要先写基础设施,这里的话主要是需要构造一个异步调用模型。异步调用类:

public class AsyncCall {

    private Random random = new Random(System.currentTimeMillis());

    private ExecutorService tp = Executors.newSingleThreadExecutor();

    //demo1,2,4,5调用方法
public void call(BaseDemo demo){ new Thread(()->{
long res = random.nextInt(10); try {
Thread.sleep(res*1000);
} catch (InterruptedException e) {
e.printStackTrace();
} demo.callback(res);
}).start(); } //demo3调用方法
public Future<Long> futureCall(){ return tp.submit(()-> {
long res = random.nextInt(10); try {
Thread.sleep(res*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return res;
}); } public void shutdown(){ tp.shutdown(); } }

我们主要关心call方法,这个方法接收了一个demo参数,并且开启了一个线程,在线程中执行具体的任务,并利用demo的callback方法进行回调函数的调用。大家注意到了这里的返回结果就是一个[0,10)的长整型,并且结果是几,就让线程sleep多久——这主要是为了更好地观察实验结果,模拟异步调用过程中的处理时间。

至于futureCall和shutdown方法,以及线程池tp都是为了demo3利用Future来实现做准备的。

demo的基类:

public abstract class BaseDemo {

    protected AsyncCall asyncCall = new AsyncCall();

    public abstract void callback(long response);

    public void call(){
System.out.println("发起调用");
asyncCall.call(this);
System.out.println("调用返回");
} }

BaseDemo非常简单,里面包含一个异步调用类的实例,另外有一个call方法用于发起异步调用,当然还有一个抽象方法callback需要每个demo去实现的——主要在回调中进行相应的处理来达到异步调用转同步的目的。

1. 使用wait和notify方法

这个方法其实是利用了锁机制,直接贴代码:

public class Demo1 extends BaseDemo{

    private final Object lock = new Object();

    @Override
public void callback(long response) {
System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束"); synchronized (lock) {
lock.notifyAll();
} } public static void main(String[] args) { Demo1 demo1 = new Demo1(); demo1.call(); synchronized (demo1.lock){
try {
demo1.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println("主线程内容"); }
}

可以看到在发起调用后,主线程利用wait进行阻塞,等待回调中调用notify或者notifyAll方法来进行唤醒。注意,和大家认知的一样,这里wait和notify都是需要先获得对象的锁的。在主线程中最后我们打印了一个内容,这也是用来验证实验结果的,如果没有wait和notify,主线程内容会紧随调用内容立刻打印;而像我们上面的代码,主线程内容会一直等待回调函数调用结束才会进行打印。

没有使用同步操作的情况下,打印结果:

发起调用
调用返回
主线程内容
得到结果
1
调用结束

而使用了同步操作后:

发起调用
调用返回
得到结果
9
调用结束
主线程内容

2. 使用条件锁

和方法一的原理类似:

public class Demo2 extends BaseDemo {

    private final Lock lock = new ReentrantLock();
private final Condition con = lock.newCondition(); @Override
public void callback(long response) { System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束");
lock.lock();
try {
con.signal();
}finally {
lock.unlock();
} } public static void main(String[] args) { Demo2 demo2 = new Demo2(); demo2.call(); demo2.lock.lock(); try {
demo2.con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
demo2.lock.unlock();
}
System.out.println("主线程内容");
}
}

基本上和方法一没什么区别,只是这里使用了条件锁,两者的锁机制有所不同。

3. Future

使用Future的方法和之前不太一样,我们调用的异步方法也不一样。

public class Demo3{

    private AsyncCall asyncCall = new AsyncCall();

    public Future<Long> call(){

        Future<Long> future = asyncCall.futureCall();

        asyncCall.shutdown();

        return future;

    }

    public static void main(String[] args) {

        Demo3 demo3 = new Demo3();

        System.out.println("发起调用");
Future<Long> future = demo3.call();
System.out.println("返回结果"); while (!future.isDone() && !future.isCancelled()); try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} System.out.println("主线程内容"); }
}

我们调用futureCall方法,方法中会想线程池tp提交一个Callable,然后返回一个Future,这个Future就是我们demo3中call中得到的,得到future对象之后就可以关闭线程池啦,调用asyncCall的shutdown方法。关于关闭线程池这里有一点需要注意,我们回过头来看看asyncCall的shutdown方法:

public void shutdown(){

    tp.shutdown();

}

发现只是简单调用了线程池的shutdown方法,然后我们说注意的点,这里最好不要用tp的shutdownNow方法,该方法会试图去中断线程中中正在执行的任务;也就是说,如果使用该方法,有可能我们的future所对应的任务将被中断,无法得到执行结果。

然后我们关注主线程中的内容,主线程的阻塞由我们自己来实现,通过future的isDone和isCancelled来判断执行状态,一直到执行完成或被取消。随后,我们打印get到的结果。

4. 使用CountDownLatch

使用CountDownLatch或许是日常编程中最常见的一种了,也感觉是相对优雅的一种:

public class Demo4 extends BaseDemo{

    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
public void callback(long response) { System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束"); countDownLatch.countDown(); } public static void main(String[] args) { Demo4 demo4 = new Demo4(); demo4.call(); try {
demo4.countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("主线程内容"); }
}

正如大家平时使用的那样,此处在主线程中利用CountDownLatch的await方法进行阻塞,在回调中利用countDown方法来使得其他线程await的部分得以继续运行。

当然,这里和demo1和demo2中都一样,主线程中阻塞的部分,都可以设置一个超时时间,超时后可以不再阻塞。

5. 使用CyclicBarrier

CyclicBarrier的情况和CountDownLatch有些类似:

public class Demo5 extends BaseDemo{

    private CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

    @Override
public void callback(long response) { System.out.println("得到结果");
System.out.println(response);
System.out.println("调用结束"); try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} } public static void main(String[] args) { Demo5 demo5 = new Demo5(); demo5.call(); try {
demo5.cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} System.out.println("主线程内容"); }
}

大家注意一下,CyclicBarrier和CountDownLatch仅仅只是类似,两者还是有一定区别的。

比如,一个可以理解为做加法,等到加到这个数字后一起运行;一个则是减法,减到0继续运行。一个是可以重复计数的;另一个不可以等等等等。

另外,使用CyclicBarrier的时候要注意两点。第一点,初始化的时候,参数数字要设为2,因为异步调用这里是一个线程,而主线程是一个线程,两个线程都await的时候才能继续执行,这也是和CountDownLatch区别的部分。第二点,也是关于初始化参数的数值的,和这里的demo无关,在平时编程的时候,需要比较小心,如果这个数值设置得很大,比线程池中的线程数都大,那么就很容易引起死锁了。

总结

综上,就是本次需要说的几种方法了。事实上,所有的方法都是同一个原理,也就是在调用的线程中进行阻塞等待结果,而在回调中函数中进行阻塞状态的解除。

如果你还有其他方法,欢迎与我讨论哦~

参考

原文链接:https://www.jianshu.com/p/f00aa6f66281

java中5种异步转同步方法的更多相关文章

  1. Java中四种引用:强、软、弱、虚引用

    这篇文章非常棒:http://alinazh.blog.51cto.com/5459270/1276173 Java中四种引用:强.软.弱.虚引用 1.1.强引用当我们使用new 这个关键字创建对象时 ...

  2. Java中几种日志方案

    .本文记录Java中几种常用的日志解决方案 0x01 Log4j .这应该是一个比较老牌的日志方案了,配置也比较简单,步骤如下 1)添加对应依赖,比如 Gradle 中 dependencies { ...

  3. java中四种引用类型

    java中四种引用类型  今天看代码,里面有一个类java.lang.ref.SoftReference把小弟弄神了,试想一下,接触java已经有3年了哇,连lang包下面的类都不了解,怎么混.后来在 ...

  4. JAVA 中两种判断输入的是否是数字的方法__正则化_

    JAVA 中两种判断输入的是否是数字的方法 package t0806; import java.io.*; import java.util.regex.*; public class zhengz ...

  5. [js]javascript中4种异步

    javascript中4种异步: 1.ajax 2.定时器 3.事件绑定 4,回调 定时器 //顺序执行 /* var s = 0; for (var i = 0; i < 10000; i++ ...

  6. Java中几种常用数据类型之间转换的方法

    Java中几种常用的数据类型之间转换方法: 1. short-->int 转换 exp: short shortvar=0; int intvar=0; shortvar= (short) in ...

  7. Java基础-Java中23种设计模式之常用的设计模式

    Java基础-Java中23种设计模式之常用的设计模式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.设计模式分类 设计模式是针对特定场景给出的专家级的解决方案.总的来说设 ...

  8. java中8种数据类型和默认值所占字节数

    java 8种基本数据类型的默认值及所占字节数 通过一段代码来测试一下 8种基本数据类型的默认值 1 package dierge; 2 3 public class Ceshi { 4 int a; ...

  9. Java中23种经典设计模式详解

    Java中23种设计模式目录1. 设计模式 31.1 创建型模式 41.1.1 工厂方法 41.1.2 抽象工厂 61.1.3 建造者模式 101.1.4 单态模式 131.1.5 原型模式 151. ...

随机推荐

  1. hihocoeder1384

    hihocoeder1384 算法竞赛进阶指南上的题目 我们肯定是吧最大值和最小值匹配,次大值和次小值匹配以此类推 首先,类似于区间覆盖的思想,我们对于一个\(L\),找到最大的满足条件的\(R\) ...

  2. poj2826 An Easy Problem?!(计算几何)

    传送门 •题意 两根木块组成一个槽,给定两个木块的两个端点 雨水竖直下落,问槽里能装多少雨水, •思路 找不能收集到雨水的情况 我们令线段较高的点为s点,较低的点为e点 ①两条木块没有交点 ②平行或重 ...

  3. 使用Glide加载圆角图片

    //设置图片圆角角度 RoundedCorners roundedCorners= new RoundedCorners(6); //通过RequestOptions扩展功能,override采样率, ...

  4. 22.json&pickle&shelve

    转载:https://www.cnblogs.com/yuanchenqi/article/5732581.html json 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  5. 转载:通过监控Nginx日志来实时屏蔽高频恶意访问的IP

    通过监控Nginx日志来实时屏蔽高频恶意访问的IP   目前在我的VPS上主要通过两种方式来限制ip的访问次数. 通过Nginx的limit_req配置来限制同一ip在一分钟内的访问次数 通过Ngin ...

  6. 关于 5G,我们应该了解的

    2020年元旦,我看了高通之前发布的一篇关于5G的文章,标题为“Everything You Need to Know About 5G”,主要包括5G是什么.有什么优势以及未来的前景等内容.我斗胆翻 ...

  7. Qt Quick QMl学习笔记 之图片浏览器

    Qt Quick模块是编写QML应用程序的标准库.虽然Qt QML模块提供QML引擎和语言基础结构,但Qt Quick模块提供了使用QML创建用户界面所需的所有基本类型.它提供了一个可视画布,包括用于 ...

  8. [**P2766** 最长不下降子序列问题](https://www.luogu.org/problemnew/show/P2766)

    P2766 最长不下降子序列问题 考虑我们是如何\(dp\)这个\(LIS\)的. 我们是倒着推,设置\(dp(i)\)代表以\(i\)为起点的\(LIS\)是多少.转移太显然了 \[ dp(i)=m ...

  9. 三分钟学会使用Docker部署.NET Core

    大概快有一年的时间没有碰过docker了,理由很简单,基本上都是在IIS上部署,看到很多大佬都开始Devops持续化集成了,但相对来说成本会更高,但对于大型团队来说还是不错的,这不?不想被大伙甩下,哈 ...

  10. 浅谈Java中接口与抽象类的异同

    浅谈Java中接口与抽象类的异同 抽象类和接口这两个概念困扰了我许久,在我看来,接口与抽象类真的十分相似.期间也曾找过许许多多的资料,参考了各路大神的见解,也只能是简简单单地在语法上懂得两者的区别.硬 ...