Java中如何实现协程、同步与异步操作?

2026-06-11 02:233阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2410个文字,预计阅读时间需要10分钟。

Java中如何实现协程、同步与异步操作?

前言:最近在研究程序设计,输出几篇文章来介绍一些协同程序。协同程序与异步有非常大的关联性,想先介绍一下异步。

异步是一种程序的运行方式。异步编程是一种编程范式,它允许程序在等待某些操作完成时继续执行其他任务。在异步编程中,程序不会阻塞,而是会释放控制权,等待某个事件或条件发生后再继续执行。这种方式可以显著提高程序的效率和响应性。

异步编程在多种编程语言和框架中都有实现,以下是一些常见的异步编程语言和框架:

1. JavaScript(Node.js):Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许在服务器端使用 JavaScript 进行异步编程。

2.Python:Python 的 `asyncio` 模块提供了异步编程的框架,允许使用 `async` 和 `await` 语法。

3.Java:Java 的 `CompletableFuture` 类提供了异步编程的支持,允许以声明式的方式编写异步代码。

4.C#:C# 的 `async` 和 `await` 语法使得异步编程变得简单易用。

在异步编程中,常见的概念包括:

- 回调(Callback):回调函数在异步操作完成后执行。

- 事件(Event):事件是异步编程中的一种机制,允许程序在事件发生时执行相应的代码。- Promise:Promise 是一个对象,它表示一个尚未完成但可能完成的操作,并且提供了异步操作成功或失败时的回调机制。

Java中如何实现协程、同步与异步操作?

总结:异步编程是一种强大的编程范式,它使得程序在等待操作完成时能够继续执行其他任务。通过学习异步编程,可以编写出更高效、响应更快的程序。

前言最近在学习协程打算输出几篇文章来介绍一下协程。而协程与异步有很大的关联所以想先介绍一下异步。异步是一种程序的运行方式各种编程语

前言  

    最近在学习协程,打算输出几篇文章来介绍一下协程。而协程与异步有很大的关联,所以想先介绍一下异步。

异步是一种程序的运行方式,各种编程语言语言或多或少都对它有所支持。异步对于Java后端程序员来说并不是一种特别熟悉的概念,而安卓或者前端的同学可能会对异步这个概念会更熟悉一些。

程序同步和异步

同步是最简单也是最符合我们人类思维方式的编程方式,所谓同步,就是程序会按照代码一行行执行,执行完一句再执行下一句。

同步代码看起来是这样:

stepA();stepB();stepC();...

stepA执行完后,开始执行stepB,stepB执行完后,执行stepC。

而有时候我们会有这样的需求:在后台执行一段程序。具体到我们这个案例来说,就是执行完stepA后,要开始执行stepB,但不用等stepB执行完,现在可以立即执行stepC。

于是异步编程就出来了。在Java语言里,我们可以创建一个新的线程(或者使用线程池)去执行异步任务:

stepA();new Thread(() -> stepB()).start();stepC();

      这样,stepB就在另一个线程里面“异步”执行了,而stepC还是继续在当前线程里执行。

      异步有什么好处呢?

      有一个显而易见的好处:让程序“响应更快”。比如上述的case,如果stepB()任务比较耗时,比如发邮件操作。那使用同步的方式,程序需要等待卡在这里stepB完成才能往下走。而如果使用异步的方式,可以让stepB“后台”执行,不影响当前程序往下执行。

      这在UI程序中尤为重要,毕竟界面的响应时间对用户的体验很大。所以涉及到UI的语言、框架是最先研究和尝试异步技术的。比如RxJava起源于安卓,Kotlin、Dart、Javascript等语言也在UI程序中用得比较多。

      而同样的,对于IO密集型的程序,使用异步也能够明显提升性能,大家熟悉的nginx、redis、netty等,其底层都是利用的操作系统的系统调用(比如Linux的epoll)来实现异步,达到高性能的表现。

使用异步

在Java中使用异步一般是用多线程来实现的。

正如我们上文提到的,我们可以启动一个新的线程去“后台”执行一个异步任务。当然,我们也可以把它扔进线程池里。

// 新建线程执行异步任务new Thread(() -> stepB()).start();

但如果我们要使用异步的返回结果怎么办呢?比如常见的场景是请求另一个微服务的接口。

JDK 1.5提供了Callable和Future接口,用于实现“有返回值”的多线程任务。使用的时候一般是配合线程池使用:

public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newSingleThreadExecutor();Future future = executor.submit(() -> {// 模拟IO需要一秒Thread.sleep(1000);return "hello";});System.out.println("submitted");// 这里会阻塞直到future.get返回值或者超时System.out.println(future.get(2, TimeUnit.SECONDS));executor.shutdown();}

     如果使用Future,我们在调用future.get()方法的时候,会阻塞直到异步任务返回结果或者抛异常或者超时。试想一下我们有这个需求:任务B1需要任务B的结果,任务C1需要任务C的结果,但它们彼此是独立的。如果使用Future我们得这样做:

stepA();Future futureB = executor.submit(() -> stepB());Future futureC = executor.submit(() -> stepC());stepB1(futureB.get());// 这一步必须等stepB1执行完stepC1(futureC.get());

所以使用future其实还是会在调用get方法的时候阻塞主流程。那有没有什么办法不阻塞呢?解决办法是使用回调。

回调与回调地狱

所谓回调,在函数式编程语言中的说法就是,我传一个函数进去,等异步任务完成后,就执行这个函数。Java虽然不是函数式编程语言,但Java8也支持函数式编程。

假设我们的需求仅仅是把一个异步任务产生的结果字符串打印出来,我们可以这样写:

public static void main(String[] args) throws Exception {Consumer callback = System.out::println;new Thread(() -> {// 模拟api调用,省略try-catchThread.sleep(1000);// 假设这是调用第三方api返回的字符串String s = "hello";callback.accept(s);}).start();System.out.println("started");}

甚至可以不用callback函数,直接在把程序代码段放到异步任务里面:

public static void main(String[] args) throws Exception {new Thread(() -> {// 模拟api调用,省略try-catchThread.sleep(1000);// 假设这是调用第三方api返回的字符串String s = "hello";print(s);}).start();System.out.println("started");}private static void print(String str) {System.out.println(str);}

那如果异步任务需要的回调太多呢?比如我们需要先异步请求接口A,拿到结果后再去异步请求接口B,拿到结果后再去异步请求接口C:

public static void main(String[] args) throws Exception {new Thread(() -> {String resultA = callAPI("input", "a");new Thread(() -> {String resultB = callAPI(resultA, "b");new Thread(() -> {String resultC = callAPI(resultB, "c");System.out.println(resultC);}).start();}).start();}).start();System.out.println("started");}private static String callAPI(String param, String mockRes) {// 模拟api调用,省略try-catchThread.sleep(1000);return mockRes;}

有没有感觉这层层嵌套的代码比较难看?这就是臭名昭著的“回调地狱”。

Java 8提供了一个叫CompletableFuture类来支持一些异步功能,包括回调。它支持「链式调用」,可以在一定程度上解决“回调地狱”的问题。上述代码可以用CompletableFuture这样写:

public static void main(String[] args) throws Exception {CompletableFuture.supplyAsync(() -> callAPI("input", "a")).thenApply(res -> callAPI(res, "b")).thenApply(res -> callAPI(res, "c")).thenAccept(System.out::println);System.out.println("started");// 等异步任务输出Thread.sleep(20000);}

响应式编程

响应式编程是另一种异步解决方案。它的主要应用场景是异步处理数据集合。对标的是同步的Iterable。这里有一个对比图:

      比较典型的场景是UI产生的事件流(比如点击事件等)。

      响应式编程的核心是“观察者模式”。客户端发送请求和,能够立即得到一个Stream返回,客户端订阅这个Stream来接收通知。等服务端有数据时,就会往Stream上发布数据,客户端就能够收到数据了。

      Spring 5也支持响应式编程,并认为它将是未来web编程的一大趋势。响应流 API java.util.concurrent.flow 已正式成为 Java 9 的一部分。但目前发展还比较缓慢,大家对这个东西的接受度一般,可能是因为切换成本比较高,且目前webmvc能够满足大多数需求吧。

协程

看了一圈资料,很多文章在讨论协程是什么。我初步总结下来协程主要有两个作用:

  • 可以用同步的方式写异步代码
  • 可以在适当的时候挂起当前程序片段,在适当的时候恢复,这是代码可以控制的

协程由程序控制,在同一个线程内部工作,在IO成为瓶颈的绝大多数应用场景下,可以代替当前主流的多线程模型,省去线程切换的开销,提升吞吐量。  

标签:实现前言

本文共计2410个文字,预计阅读时间需要10分钟。

Java中如何实现协程、同步与异步操作?

前言:最近在研究程序设计,输出几篇文章来介绍一些协同程序。协同程序与异步有非常大的关联性,想先介绍一下异步。

异步是一种程序的运行方式。异步编程是一种编程范式,它允许程序在等待某些操作完成时继续执行其他任务。在异步编程中,程序不会阻塞,而是会释放控制权,等待某个事件或条件发生后再继续执行。这种方式可以显著提高程序的效率和响应性。

异步编程在多种编程语言和框架中都有实现,以下是一些常见的异步编程语言和框架:

1. JavaScript(Node.js):Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许在服务器端使用 JavaScript 进行异步编程。

2.Python:Python 的 `asyncio` 模块提供了异步编程的框架,允许使用 `async` 和 `await` 语法。

3.Java:Java 的 `CompletableFuture` 类提供了异步编程的支持,允许以声明式的方式编写异步代码。

4.C#:C# 的 `async` 和 `await` 语法使得异步编程变得简单易用。

在异步编程中,常见的概念包括:

- 回调(Callback):回调函数在异步操作完成后执行。

- 事件(Event):事件是异步编程中的一种机制,允许程序在事件发生时执行相应的代码。- Promise:Promise 是一个对象,它表示一个尚未完成但可能完成的操作,并且提供了异步操作成功或失败时的回调机制。

Java中如何实现协程、同步与异步操作?

总结:异步编程是一种强大的编程范式,它使得程序在等待操作完成时能够继续执行其他任务。通过学习异步编程,可以编写出更高效、响应更快的程序。

前言最近在学习协程打算输出几篇文章来介绍一下协程。而协程与异步有很大的关联所以想先介绍一下异步。异步是一种程序的运行方式各种编程语

前言  

    最近在学习协程,打算输出几篇文章来介绍一下协程。而协程与异步有很大的关联,所以想先介绍一下异步。

异步是一种程序的运行方式,各种编程语言语言或多或少都对它有所支持。异步对于Java后端程序员来说并不是一种特别熟悉的概念,而安卓或者前端的同学可能会对异步这个概念会更熟悉一些。

程序同步和异步

同步是最简单也是最符合我们人类思维方式的编程方式,所谓同步,就是程序会按照代码一行行执行,执行完一句再执行下一句。

同步代码看起来是这样:

stepA();stepB();stepC();...

stepA执行完后,开始执行stepB,stepB执行完后,执行stepC。

而有时候我们会有这样的需求:在后台执行一段程序。具体到我们这个案例来说,就是执行完stepA后,要开始执行stepB,但不用等stepB执行完,现在可以立即执行stepC。

于是异步编程就出来了。在Java语言里,我们可以创建一个新的线程(或者使用线程池)去执行异步任务:

stepA();new Thread(() -> stepB()).start();stepC();

      这样,stepB就在另一个线程里面“异步”执行了,而stepC还是继续在当前线程里执行。

      异步有什么好处呢?

      有一个显而易见的好处:让程序“响应更快”。比如上述的case,如果stepB()任务比较耗时,比如发邮件操作。那使用同步的方式,程序需要等待卡在这里stepB完成才能往下走。而如果使用异步的方式,可以让stepB“后台”执行,不影响当前程序往下执行。

      这在UI程序中尤为重要,毕竟界面的响应时间对用户的体验很大。所以涉及到UI的语言、框架是最先研究和尝试异步技术的。比如RxJava起源于安卓,Kotlin、Dart、Javascript等语言也在UI程序中用得比较多。

      而同样的,对于IO密集型的程序,使用异步也能够明显提升性能,大家熟悉的nginx、redis、netty等,其底层都是利用的操作系统的系统调用(比如Linux的epoll)来实现异步,达到高性能的表现。

使用异步

在Java中使用异步一般是用多线程来实现的。

正如我们上文提到的,我们可以启动一个新的线程去“后台”执行一个异步任务。当然,我们也可以把它扔进线程池里。

// 新建线程执行异步任务new Thread(() -> stepB()).start();

但如果我们要使用异步的返回结果怎么办呢?比如常见的场景是请求另一个微服务的接口。

JDK 1.5提供了Callable和Future接口,用于实现“有返回值”的多线程任务。使用的时候一般是配合线程池使用:

public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newSingleThreadExecutor();Future future = executor.submit(() -> {// 模拟IO需要一秒Thread.sleep(1000);return "hello";});System.out.println("submitted");// 这里会阻塞直到future.get返回值或者超时System.out.println(future.get(2, TimeUnit.SECONDS));executor.shutdown();}

     如果使用Future,我们在调用future.get()方法的时候,会阻塞直到异步任务返回结果或者抛异常或者超时。试想一下我们有这个需求:任务B1需要任务B的结果,任务C1需要任务C的结果,但它们彼此是独立的。如果使用Future我们得这样做:

stepA();Future futureB = executor.submit(() -> stepB());Future futureC = executor.submit(() -> stepC());stepB1(futureB.get());// 这一步必须等stepB1执行完stepC1(futureC.get());

所以使用future其实还是会在调用get方法的时候阻塞主流程。那有没有什么办法不阻塞呢?解决办法是使用回调。

回调与回调地狱

所谓回调,在函数式编程语言中的说法就是,我传一个函数进去,等异步任务完成后,就执行这个函数。Java虽然不是函数式编程语言,但Java8也支持函数式编程。

假设我们的需求仅仅是把一个异步任务产生的结果字符串打印出来,我们可以这样写:

public static void main(String[] args) throws Exception {Consumer callback = System.out::println;new Thread(() -> {// 模拟api调用,省略try-catchThread.sleep(1000);// 假设这是调用第三方api返回的字符串String s = "hello";callback.accept(s);}).start();System.out.println("started");}

甚至可以不用callback函数,直接在把程序代码段放到异步任务里面:

public static void main(String[] args) throws Exception {new Thread(() -> {// 模拟api调用,省略try-catchThread.sleep(1000);// 假设这是调用第三方api返回的字符串String s = "hello";print(s);}).start();System.out.println("started");}private static void print(String str) {System.out.println(str);}

那如果异步任务需要的回调太多呢?比如我们需要先异步请求接口A,拿到结果后再去异步请求接口B,拿到结果后再去异步请求接口C:

public static void main(String[] args) throws Exception {new Thread(() -> {String resultA = callAPI("input", "a");new Thread(() -> {String resultB = callAPI(resultA, "b");new Thread(() -> {String resultC = callAPI(resultB, "c");System.out.println(resultC);}).start();}).start();}).start();System.out.println("started");}private static String callAPI(String param, String mockRes) {// 模拟api调用,省略try-catchThread.sleep(1000);return mockRes;}

有没有感觉这层层嵌套的代码比较难看?这就是臭名昭著的“回调地狱”。

Java 8提供了一个叫CompletableFuture类来支持一些异步功能,包括回调。它支持「链式调用」,可以在一定程度上解决“回调地狱”的问题。上述代码可以用CompletableFuture这样写:

public static void main(String[] args) throws Exception {CompletableFuture.supplyAsync(() -> callAPI("input", "a")).thenApply(res -> callAPI(res, "b")).thenApply(res -> callAPI(res, "c")).thenAccept(System.out::println);System.out.println("started");// 等异步任务输出Thread.sleep(20000);}

响应式编程

响应式编程是另一种异步解决方案。它的主要应用场景是异步处理数据集合。对标的是同步的Iterable。这里有一个对比图:

      比较典型的场景是UI产生的事件流(比如点击事件等)。

      响应式编程的核心是“观察者模式”。客户端发送请求和,能够立即得到一个Stream返回,客户端订阅这个Stream来接收通知。等服务端有数据时,就会往Stream上发布数据,客户端就能够收到数据了。

      Spring 5也支持响应式编程,并认为它将是未来web编程的一大趋势。响应流 API java.util.concurrent.flow 已正式成为 Java 9 的一部分。但目前发展还比较缓慢,大家对这个东西的接受度一般,可能是因为切换成本比较高,且目前webmvc能够满足大多数需求吧。

协程

看了一圈资料,很多文章在讨论协程是什么。我初步总结下来协程主要有两个作用:

  • 可以用同步的方式写异步代码
  • 可以在适当的时候挂起当前程序片段,在适当的时候恢复,这是代码可以控制的

协程由程序控制,在同一个线程内部工作,在IO成为瓶颈的绝大多数应用场景下,可以代替当前主流的多线程模型,省去线程切换的开销,提升吞吐量。  

标签:实现前言