Java多线程学习笔记(五) 长乐无极

简介: Java多线程学习笔记(五) 长乐无极

前言

有段时间在研究WebSocket和HTTP2.0版本,JDK11的新的HttpClient支持HTTP/2和WebSocket,于是我就尝试着用了用,然后就发现了CompletableFuture这个类, 我当时写的代码如下:

public static void main(String[] args) {
   HttpClient httpClient = HttpClient.newBuilder().version(Version.HTTP_2).build(); 
   httpClient.version();
   CompletableFuture<WebSocket> result = httpClient.newWebSocketBuilder().buildAsync(null, null);
   Class<? extends HttpClient> clazz = httpClient.getClass();   
 }

然后我就点进去看了下CompletableFuture的源码,发现这个类自1.8被引入,也是关于异步编程的。该类实现了Future和CompletionStage接口. Future我们前面的文章已经讨论过了,所以CompletableFuture具备Future的特性,而CompletionStage是什么呢?首先这个类名我们应该怎么理解, CompletionStage是一个合成词:

  • Completion:  完成, 结束。
  • Stage: 步骤,步,阶段

所以我们姑且就可以将CompletionStage理解为完成步骤或者完成阶段。在研究CompletableFuture,我们先看下CompletionStage。本篇需要Lambda表达式的相关基础, 如果对Lambda表达式还不是很了解,可以参看我在掘金写的文章:

  • Lambda表达式 函数式编程 Stream API学习笔记

先看CompletionStage

先看注释

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages. The functionality defined in this interface takes only a few basic forms, which expand out to a larger set of methods to capture a range of usage styles:

一个异步计算的步骤可能表现为一个动作或者另一个CompletionStage(完成阶段)完成时计算值。一个步骤在计算完成时结束,但是这个步骤也可能触发其他步骤。在CompletionStage接口中定义了一些基本形式的函数,这些方法可以被扩展以适应不同的风格。

  • The computation performed by a stage may be expressed as a Function, Consumer, or Runnable (using methods with names including apply, accept, or run, respectively) depending on whether it requires arguments and/or produces results. For example, stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()). An additional form (compose) applies functions of stages themselves, rather than their results. 这一类步骤的计算被表现为Function(Lambda表达式,函数式接口)、Consumer(Lambda表达式 函数式接口)、Runnable(方法名包括apply、accept、run) 例子:
stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()
//简单讲述下这段代码的意思, thenApply拿到接收方法的值,传递给thenAccept,然后在thenRun.

另一种形式的用法是应用步骤本身的函数而不是他们的结果。

  • One stage's execution may be triggered by completion of a single stage, or both of two stages, or either of two stages. Dependencies on a single stage are arranged using methods with prefix then. Those triggered by completion of both of two stages may combine their results or effects, using correspondingly named methods. Those triggered by either of two stages make no guarantees about which of the results or effects are used for the dependent stage's computation. 一个步骤的执行可能被另一个步骤或者两个步骤完成时,两个步骤中任意一个步骤完成触发.  依赖于一个步骤的用前缀为then的方法。被两个步骤完成才触发且需要使用结果的可以使用方法名包含combine的方法。两个步骤任意一个步骤完成都可以触发的步骤,下一个步骤所接收的结果无法保证是哪一个步骤,可以使用方法名宝行Either。
  • Dependencies among stages control the triggering of computations, but do not otherwise guarantee any particular ordering. Additionally, execution of a new stage's computations may be arranged in any of three ways: default execution, default asynchronous execution (using methods with suffix async that employ the stage's default asynchronous execution facility), or custom (via a supplied Executor). The execution properties of default and async modes are specified by CompletionStage implementations, not this interface. Methods with explicit Executor arguments may have arbitrary execution properties, and might not even support concurrent execution, but are arranged for processing in a way that accommodates asynchrony.

如果是各个步骤之间的计算和触发不要求特定的顺序,就可以考虑异步执行,步骤的执行有三种形式:  默认执行、异步执行(方法名带有async的,在执行的时候就是异步执行), 定制(支持接收执行者)。默认和异步模式的执行属性由CompletionStage实现指定,而不是在这个接口,具有显式Executor参数的方法可能具有任意的执行属性,甚至可能不支持并发执行,但其处理方式可以适应异步。

  • Two method forms support processing whether the triggering stage completed normally or exceptionally: Method whenComplete allows injection of an action regardless of outcome, otherwise preserving the outcome in its completion. Method handle additionally allows the stage to compute a replacement result that may enable further processing by other dependent stages. In all other cases, if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. If a stage is dependent on both of two stages, and both complete exceptionally, then the CompletionException may correspond to either one of these exceptions. If a stage is dependent on either of two others, and only one of them completes exceptionally, no guarantees are made about whether the dependent stage completes normally or exceptionally. In the case of method whenComplete, when the supplied action itself encounters an exception, then the stage exceptionally completes with this exception if not already completed exceptionally.

正常完成或者异常完成的触发有两个方法支持。方法whenComplete在执行的时候是忽略是正常结束还是异常结束, 如果是异常结束则whenComplete接收的值为null,异常有值。handle方法可以对上一步的结果再进行计算,让下一个阶段接着处理。再有,如果其他步骤因为发成了异常和错误而结束,所有依赖此步骤的步骤将会被完成(completeExceptionally方法),completeExceptionally持有异常完成的原因。如果一个步骤依赖于某两个步骤中的一个步骤,如果被依赖的两个步骤意外结束,不保证下一个步骤的正常完成还是异常完成。如果whenComplete接收的动作发生了异常,下一个阶段也会携带whenComplete方法发生的异常。

我的体会

如果某项任务比较大,我们通常会将浙这些任务进行拆解,分工。在一些步骤完成之后,完成之后进行下一步骤,这在现实生活中是相当常见的。那么软件的世界呢,在Java中呢,我们需要委托线程进行工作呢? 工作模型像下面这样:image.png我们可以使用的接口有哪些呢?想想之前的线程协作:

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Future
  • FutureTask

这些组合起来去完成我们提出的目标, 似乎有点费手脚,CompletionStage就为解决这样的计算任务而生,将任务拆解为一个一个的步骤,任务完成返回CompletionStage。注释提到的任务模型:

  • 一个接一个(一个任务触发完成之后来到下一个任务)  方法名中包含apply、accept、then、run对应这种任务模型
  • 多个任务完成之后来到下一个任务  方法名中包含combine对应这种任务模型
  • 多个任务中只要有一个完成就触发下一个任务 方法名中带有Either对应这种任务模型

带有async是异步执行也就是会开启一个线程执行任务。方法正常完成和异常完成触发的回调为whenComplete和handle.  但CompletionStage只提供了规范,真正要使用我们还要看它的实现类为CompletableFuture。

CompletableFuture  简单使用示例

单链任务使用示例

/**
     * 做菜任务示例
     * 先买菜,切菜,然后再吃饭
     */
    private static void makeFoodDemo() {
        CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("去买菜");
            return "买菜成功";
        }).thenApplyAsync(o -> {
            System.out.println("开始做菜");
            return "做菜完成";
        });
        // 任务执行完毕后 会触发此方法
        System.out.println(task.join()+"开始吃饭");
    }

两个都成功才触发示例

/**
     * 合并结果
     */
    private static void appointmentTaskDemo() {
        CompletableFuture<String> boyTask = CompletableFuture.supplyAsync(() -> {
            return "男孩子发出邀请";
        });
        boyTask.thenCombineAsync(CompletableFuture.supplyAsync(()->"女孩子接收邀请"),(o1,o2)->{
            System.out.println(o1);
            System.out.println(o2);
            return "开始约会";
        }).thenAccept(o-> System.out.println(o));
    }

两个任意一个成功才触发示例

/**
     * 做菜任务示例
     * 先买菜,切菜,然后再吃饭
     */
    private static void makeFoodDemo() {
        CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("去买菜");
            return "买菜成功";
        }).thenApplyAsync(o -> {
            System.out.println("开始做菜");
            return "做菜完成";
        });
        // 任务执行完毕后 会触发此方法
        System.out.println(task.join()+"开始吃饭");
    }

总结

有了CompletableFuture切割任务是不是就有够顺滑了呢。这也就是我学习的方法,有注释优先看注释,大多数情况下,注释都写的简单明了。

相关文章
|
13天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
4天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
4天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
27 1
|
12天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
12天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
11天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
14天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
17天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
4月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
124 1