编程导航并发编程话题讨论

并发编程

14 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

Java并发编程中的ABA问题解析

## Java并发编程中的ABA问题解析 **ABA问题是Java并发编程中一个隐蔽但严重的问题,它发生在使用CAS(Compare-And-Swap)操作的无锁算法中**。当一个线程读取共享变量的值为A,然后在该线程进行操作期间,其他线程将该变量修改为B,之后又改回A,此时原线程再次检查变量值时,会误认为变量未被修改,从而执行可能不正确的操作。这种看似变量值未变,实则已发生多次变化的情况,构成了ABA问题的核心。它本质上是一个"伪不变"值导致的并发陷阱,可能引发数据结构破坏、业务逻辑错误和难以调试的问题。 ### 一、ABA问题的产生机制 ABA问题的产生与CAS机制的局限性密切相关。CAS是一种无锁原子操作,其基本原理是检查内存中的值是否与预期值一致,若一致则更新为新值,否则不更新。在单线程环境中,CAS操作是可靠的;但在多线程并发环境中,特别是当线程执行时间较长时,问题就可能出现。 具体来说,当线程T1读取共享变量V的值为A,并准备在之后的操作中使用这个值进行CAS更新时,T1可能因各种原因(如调度延迟、I/O阻塞等)暂时无法执行后续操作。在这段时间内,其他线程(如T2)可能会修改V的值为B,然后T2或另一个线程T3又将V的值改回A。当T1恢复执行时,它会发现V的值仍然是A,与它最初读取的值一致,因此会认为V未被修改,进而执行基于这个"未变"值的操作。然而,实际上V的值已经经历了A→B→A的变化过程,T1的CAS操作可能破坏数据结构的一致性或导致业务逻辑错误。 **ABA问题的核心在于CAS操作仅比较变量的当前值与预期值,而无法追踪变量在操作期间的变化历史**。这种机制在某些场景下可能引发问题,尤其是在涉及复杂数据结构(如链表、栈等)的操作中。例如,在链表的节点删除操作中,如果一个线程读取了某个节点的引用,然后在操作期间该节点被临时删除并重新插入,那么原线程的CAS操作会误认为节点未被修改,从而可能导致链表结构损坏。 ### 二、ABA问题的实际危害 ABA问题在并发编程中可能带来多种危害,具体表现取决于应用场景。在数据结构层面,它可能导致结构不一致或损坏。例如,在栈操作中,如果线程T1读取栈顶节点为A,然后准备弹出该节点,此时T2可能将A弹出并压入B,随后又将B弹出并压入A。当T1恢复执行时,它会发现栈顶节点仍然是A,于是成功执行CAS操作将栈顶设置为A的下一个节点。然而,实际上T1已经"跳过了"T2的操作,导致栈结构不一致。 在业务逻辑层面,ABA问题可能引发严重错误。以银行账户为例,假设账户余额为100元,线程T1读取该值并准备进行转账操作,此时T2可能将余额增加到150元,然后T3又将余额减少回100元。当T1恢复执行时,它会认为余额未变,仍然为100元,从而可能执行错误的转账操作,导致账户余额计算错误或资金流水记录异常。 此外,ABA问题还可能导致资源管理问题。在对象池或连接池等场景中,如果一个线程获取了某个资源并准备使用,此时其他线程可能临时释放该资源并获取另一个资源,然后又释放该资源。当原线程再次检查时,资源看似可用,但实际上可能已被其他线程使用过,导致资源泄漏或重复使用等问题。 **最棘手的是,ABA问题往往难以被发现和调试**。由于它不直接导致变量值的最终改变,因此在大多数情况下,程序可能不会立即崩溃或抛出异常,而是产生难以察觉的逻辑错误。这些问题可能在特定并发条件下才会出现,且难以复现,给开发和维护带来了巨大挑战。 ### 三、Java中的解决方案 Java提供了几种有效的方法来解决ABA问题,其中最常用的是引入版本号或标记机制,使CAS操作能够感知变量的变化历史。 **AtomicStampedReference类**是Java并发包中专门用于解决ABA问题的工具。它通过将引用与一个整数"戳"(stamp)关联,每次更新操作都会增加版本号,从而确保即使引用的值相同,版本号也不同。具体使用方法如下: ```java // 初始化带版本号的引用 AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); // 线程1获取当前值和版本号 int[] stampHolder = new int[1]; String oldValue = ref.get(stampHolder); int oldStamp = stampHolder[0]; // 线程2修改值并增加版本号 ref.compareAndSet("A", "B", oldStamp, oldStamp + 1); ref.compareAndSet("B", "A", oldStamp + 1, oldStamp + 2); // 线程1尝试修改 boolean success = ref.compareAndSet(oldValue, "C", oldStamp, oldStamp + 1); // 此时success将为false,因为版本号已变化 ``` **AtomicMarkableReference类**是另一个解决方案,它使用布尔标记代替整数版本号,适用于变化不频繁的场景。虽然它只能表示两种状态,但对于某些简单的状态切换场景已经足够。 除了这些特定类,Java还提供了其他并发工具,如`LockSupport.park()`和`LockSupport.unpark()`,可以在某些场景下通过"线程让步"的方式避免ABA问题。此外,使用显式锁(如`synchronized`或`ReentrantLock`)也可以避免ABA问题,但会牺牲无锁算法的性能优势。 **在实际应用中,选择哪种解决方案取决于具体场景**。对于需要频繁修改且对一致性要求高的数据结构(如并发队列、栈等),推荐使用`AtomicStampedReference`;对于简单状态切换,可以考虑`AtomicMarkableReference`;而对于性能要求极高且变化相对简单的场景,可以考虑其他无锁算法变体。 ### 四、ABA问题的典型应用场景 ABA问题在多种并发编程场景中可能出现,其中最典型的是涉及共享数据结构的操作。在链表操作中,如果一个线程读取了某个节点的引用,并准备进行删除或修改操作,此时其他线程可能临时删除该节点并插入另一个节点,然后又删除该节点并插入原始节点。当原线程恢复执行时,它会发现节点引用未变,从而执行可能破坏链表结构的操作。 在栈操作中,ABA问题可能导致"丢失"中间操作。例如,线程T1读取栈顶节点为A,准备将其弹出;此时T2可能将A弹出并压入B,然后又将B弹出并压入A。当T1恢复执行时,它会发现栈顶节点仍然是A,于是成功执行CAS操作将栈顶设置为A的下一个节点。然而,实际上T1已经"跳过了"T2的操作,导致栈结构不一致。 在队列操作中,ABA问题可能导致"循环"或"重复"处理。例如,在并发队列中,如果一个线程读取了队列头部节点的引用,并准备将其移除;此时其他线程可能将该节点移除并插入另一个节点,然后又将该节点移除并插入原始节点。当原线程恢复执行时,它会发现头部节点引用未变,从而可能重复处理该节点。 **在实际业务场景中,ABA问题也可能导致严重后果**。例如,在分布式系统中,如果一个节点读取了共享数据的副本,并基于该副本进行操作;此时其他节点可能修改该数据并恢复原值,导致原节点基于过时信息进行操作,引发数据不一致。在金融系统中,ABA问题可能导致交易记录异常或资金计算错误,带来财务风险。 ### 五、最佳实践与预防措施 预防ABA问题需要从设计和实现两个层面入手。在设计层面,应尽量避免使用纯CAS操作实现复杂的无锁数据结构,尤其是在需要感知操作历史的场景中。可以考虑使用带有版本号或标记的CAS操作,或者采用其他并发控制机制。 在实现层面,Java提供了`AtomicStampedReference`和`AtomicMarkableReference`等工具来解决ABA问题。使用这些工具时,需要注意以下几点: 首先,版本号或标记的管理需要谨慎。每次更新操作都应递增版本号或切换标记状态,确保能够追踪变量的变化历史。例如,在使用`AtomicStampedReference`时,每次更新都应调用`compareAndSet`方法并传递更新后的版本号: ```java // 更新版本号 int newStamp = currentStamp + 1; boolean success = ref.compareAndSet(oldValue, newValue, oldStamp, newStamp); ``` 其次,需要合理处理版本号或标记的溢出问题。对于`AtomicStampedReference`,版本号是一个整数,理论上存在溢出的可能。在实际应用中,可以通过将版本号作为长整型(long)来管理,或者在设计时确保版本号不会频繁更新到最大值。 最后,需要根据具体场景选择合适的解决方案。对于简单的状态切换,可以使用`AtomicMarkableReference`;对于复杂的场景,可以使用`AtomicStampedReference`;而对于性能要求极高且变化相对简单的场景,可以考虑其他无锁算法变体。 **在实际开发中,应避免过度依赖无锁算法**。虽然无锁算法在某些场景下性能优势明显,但实现复杂且容易出错。在大多数业务场景中,使用显式锁(如`synchronized`或`ReentrantLock`)可能更为简单和安全。只有在对并发性能有极高要求且经过充分测试的情况下,才应考虑使用无锁算法。 总之,ABA问题是Java并发编程中一个需要特别关注的问题。通过理解其产生机制、潜在危害和解决方案,开发者可以更好地设计和实现高并发系统,确保程序的正确性和可靠性。在实际应用中,应根据具体场景选择合适的并发控制机制,避免不必要的复杂性和潜在风险。

深入剖析 Java synchronized

在 Java 的进阶学习中,多线程环境下对 **临界区代码的同步处理** 是一个非常重要的内容。其中,`synchronized` 关键字作为实现临界资源同步最直接、最简洁的方式,扮演着关键角色 尽管 `synchronized` 使用简单,但它的底层实现却涉及 **JVM 锁机制**、**对象头**、**Monitor**、**锁升级** 等复杂概念。很多人仅仅停留在“加锁能保证线程安全”的层面,却不知道: + 为什么 `synchronized` 既能修饰方法,也能修饰代码块? + 锁到底存储在对象的哪个部分? + `synchronized` 源码是如何实现的 + **重量级锁** 加锁解锁流程 + 锁升级是如何优化性能的? 本文将从 **使用方式**、**字节码分析**、**对象头结构**、**锁升级过程**、**性能优化** 等多个维度,深入解析 `synchronized` 的底层原理,帮助你在高并发场景下写出更高效的代码,本文大量参考了 B 站视频 UP [<font style="color:rgb(24, 25, 28);">黑马程序员满老师</font>](https://space.bilibili.com/416747307)。 #### 临界区 **临界区** 是指一段可能被多个线程同时执行的代码,而这段代码访问了 **共享资源**(变量、集合或者文件),由于多个线程同时访问这些共享资源,可能会导致数据不一致或程序行为异常,例如下面这个非常经典的例子 <img src="https://pic.code-nav.cn/post_picture/1621031759393169410/8POLyKFt5ZTsUseO.webp" alt="" width="100%" /> 两个线程同时对 `count` 进行累加和累减操作,最终结果应当为 0,但由于 `count++` 和 `count--` 操作不是原子性的而导致最后结果五花八门 这就是最常见的线程安全问题,使用 synchronzed 就能规避这个问题🙋 #### synchronized 的三种实现 `synchronized` 可以用于 **方法** 或 **代码块** 中,其主要有以下三种典型使用方式: ##### 修饰代码块 在 `synchronized` 中使用同一个 **锁对象** 锁住临界区的代码 ```java private static final Object LOCK = new Object(); Thread thread1 = new Thread(() -> { synchronized (LOCK) { for (int i = 0; i < LOOP_NUM; i++) { count++; } } }); Thread thread2 = new Thread(() -> { synchronized (LOCK) { for (int i = 0; i < LOOP_NUM; i++) { count--; } } }); ``` 这是最灵活的方法,通过锁住具体的代码可以实现更细粒度的锁控制 ##### 修饰实例方法 `synchronized` 也可以加载实例方法上,相当于对方法整体加锁,加锁对象为 **当前实例对象** ```java class ThreadSafeCounter { private int count = 0; public synchronized void increment() { count++; } public synchronized void decrement() { count--; } public int getCount() { return this.count; } } ThreadSafeCounter threadSafeCounter = new ThreadSafeCounter(); Thread thread1 = new Thread(() -> { for (int i = 0; i < LOOP_NUM; i++) { threadSafeCounter.increment(); } }) Thread thread2 = new Thread(() -> { for (int i = 0; i < LOOP_NUM; i++) { threadSafeCounter.decrement(); } }); ``` 使用的时候创建出 **实例对象**,等同于 `synchronized(this)`,当然创建出两个实例,两个实例之间的对象加锁是互不干扰的 ##### 修饰静态方法 `synchronized` 关键字还可以加在 `静态方法` 上,加锁对象为当前 `class 类` ```java private static synchronized void increment() { for (int i = 0; i < LOOP_NUM; i++) { count--; } } ``` 例如下面两个方法,加锁的对象就是 `SynchronizedLearning.class` ```java Thread thread2 = new Thread(SynchronizedLearning::increment); Thread thread1 = new Thread(SynchronizedLearning::decrement); ``` #### 字节码分析 对字节码的分析能让我们对 java 程序更加了解,首先对 `synchronized` 加锁的代码块进行分析,通过 `javap` 工具可以查看字节码的信息 ```java private static void lambda$main$0(); descriptor: ()V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=3, args_size=0 0: getstatic #17 // Field LOCK:Ljava/lang/Object; 3: dup 4: astore_0 5: monitorenter 6: iconst_0 7: istore_1 8: iload_1 9: ldc #19 // int 100000 11: if_icmpge 28 14: getstatic #13 // Field count:I 17: iconst_1 18: iadd 19: putstatic #13 // Field count:I 22: iinc 1, 1 25: goto 8 28: aload_0 29: monitorexit 30: goto 38 33: astore_2 34: aload_0 35: monitorexit 36: aload_2 37: athrow 38: return Exception table: from to target type 6 30 33 any 33 36 33 any ``` 上面的字节码对应了上面 thread1 的代码部分,我们主要对加锁的字节码进行重点分析 ```java 0: getstatic ``` 用于获取 **锁对象**,是本类中的静态字段 `LOCK`,并压入操作数栈中 ```java 3: dup ``` `dup` 命令用于 **复制操作数栈顶部元素**(也就是 `LOCK` 对象)原因是接下来两个地方要用这个锁对象 + `monitorenter` 加锁操作需要使用 + 如果发生异常,`monitorexit` 需要再次使用到 ```java 5: monitorenter ``` 🔒 加锁指令!为什么是 monitor,后面会提到 synchronized 在不同线程竞争情况下,采用操作系统中 monitor 实现 + JVM 会尝试获取对象锁(即对 LOCK 对象加锁) + 如果其它线程已持有锁,则会阻塞直到获取锁 从这里开始进入 `synchronized` 控制的代码块区域 ```java 29: monitorexit 30: goto 38 ...... 38: return ``` 这里 `monitorexit` 是去释放锁,执行完代码块的任务,然后 38 行🔚 方法执行结束 但是可以看到在方法执行结束之前,还有部分字节码 ```java 33: astore_2 34: aload_0 35: monitorexit 36: aload_2 37: athrow ``` 这部分就是 JVM 高明的地方,考虑到开发者都没考虑到的地方,这里要结合下面的 `Exception table` ```java Exception table: from to target type 6 30 33 any 33 36 33 any ``` 这张表里记录了所有可能 **出现异常的代码块部分** 和 处理部分 这里第一行 `6 - 30` 行代码是整个加锁和处理部分,如果这部分代码出现了任何异常,字节码都会跳到 `33` 行字节码开始执行,也就是上面部分代码 这里就做了一件事情,`monitorexit` 去释放 `synchronized` 加的锁,防止出现这部分代码一直占住锁 这部分是 JVM 给我们生成的,开发者不需要去考虑 `synchronized` 代码块中出现异常时 **锁释放** 问题,这也正是 JVM 厉害👍的地方 以上是 synchronized 针对代码块进行加锁,如果是针对 **实例方法** 或者 **静态方法** 加锁,对应的方法字节码如下 ```java public synchronized void increment(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field count:I 5: iconst_1 6: iadd 7: putfield #2 // Field count:I 10: return LineNumberTable: line 89: 0 line 90: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lioc/ThreadSafeCounter; ``` 其中 flags 中会记录 `ACC_SYNCHRONIZED`,标志这个方法被 synchronized 关键词修饰,如果是 **静态方法(ACC_STATIC)**,JVM 就会锁住 `class`,如果是 **实例方法**,则锁住对象本身(`this`) ```java private static void lambda$main$0(ioc.ThreadSafeCounter); descriptor: (Lioc/ThreadSafeCounter;)V flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC ``` #### Mark Word 在 JVM 中,每个对象在堆内存中的存储结构分为 **对象头**、**实例数据**、**对齐填充** 三部分: + **对象头** 是 JVM 为每个对象预留的**元信息区域**,最重要的内容有两部分 - **Mark Word**,后面将会重点介绍 - **Klass Pointer**,是指向 **元数据(Class Metadata)**,找到自己的类信息,包括字段定义、方法表、接口、父类、GC信息等等 + **实例对象**,用于存储类的字段信息 + **对齐填充(padding)**,为了提高内存访问效率,JVM 要求对象大小是 8 字节的整数倍,若对象头 + 实例数据加起来不是 8 的倍数,就会 **自动添加填充字节** **Mark Word** 是 `synchronized` 实现锁的关键,存储了当前锁的类型和一些对象状态,下面是 64 位 JVM 中 Mark Word 的结构,在不同锁状态下 Mark Word 存储内容也不同 ```java |--------------------------------------------------------------------------------|--------------------| | Mark Word (64 bits) | State | |--------------------------------------------------------------------------------|--------------------| | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | lock: 01 | Normal | |--------------------------------------------------------------------------------|--------------------| | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock: 01 | Biased | |--------------------------------------------------------------------------------|--------------------| | ptr_to_lock_record:62 | lock: 00 | Lightweight Locked | |--------------------------------------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:62 | lock: 10 | Heavyweight Locked | |--------------------------------------------------------------------------------|--------------------| | | lock: 11 | Marked for GC | |--------------------------------------------------------------------------------|--------------------| ``` 其中 lock 位表示了当前对象的不同加锁状态: + `01`:无锁 / 偏向锁 + `00`:轻量级锁 + `10`:重量级锁 + `11`:GC 标记 接下来结合 synchronized 锁升级的过程来详细讲述 Mark Word 中的内容 #### synchronized 锁升级 这里为了能让 Java 输出 **Mark Word** 数据,使用了 `JOL` 工具来查看当前对象 **Mark Word** 信息,但 JOL 生成对象头信息过多,不方便我们查看,这里我自己写了一个工具类将对象头中 Mark Word 信息展示出来,并转成 **二进制**,方便我们查看 ```xml <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core --> <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.17</version> </dependency> ``` 在 JDK 15 之后废弃⚠️偏向锁,考虑到目前 JDK 都已经升级到 24版本,**不对偏向锁做介绍** ##### 无锁状态 对象刚创建时,属于无锁状态 ```java Object lock = new Object(); System.out.println(MarkWordUtils.getMarkWordBinary(lock)); ``` 通过 `JOL` 工具在控制台打印出了 `lock` **对象头信息** ```java 0000000000000000000000000000000000000000000000000000000000000001 ``` 如上面介绍的 `lock` 为 `01`,`biased_lock` 为 `0`,表示这个对象处于 **无锁状态** ##### 轻量级锁 ###### 1)加锁场景 当两个或者多个线程 **交替** 执行同步代码块,注意 **没有竞争!没有竞争!没有竞争!** ```java System.out.println("锁创建时 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t\t锁状态:" + MarkWordUtils.getLock(lock)); Thread t1 = new Thread(() -> { synchronized (lock) { System.out.println("t1 占用锁 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); } }); t1.start(); t1.join(); // 阻塞等待,保证 t1 线程执行完成再执行 t2 线程 Thread t2 = new Thread(() -> { synchronized (lock) { System.out.println("t2 占用锁 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); } }); t2.start(); t2.join(); System.out.println("锁全部释放 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); ``` 在上面的代码中,`t1` 和 `t2` 虽然都是使用 `lock` 作为锁对象,但是两个线程并 **不是同时加锁**,轮流使用 lock 对象,下面是输出内容 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/8byVAF4P6aYINeW5.webp) 可以看到当 `t1` 进入同步代码块的时候,`lock` 的 **Mark Word** 后两位变成了 `00`,即锁当前状态为轻量级锁 ###### 2)源码分析 这里查看 `synchronized` 加锁的 **jdk 17 源码** 来分析轻量级加锁过程,代码位置在 `src/hotspot/share/runtime/synchronizer.hpp` 中 ```cpp void ObjectSynchronizer::enter(Handle obj, BasicLock*lock, JavaThread*current) { // ... 前面代码省略 ... markWord mark = obj -> mark(); assert (!mark.has_bias_pattern(),"should not see bias pattern here"); if (mark.is_neutral()) { lock -> set_displaced_header(mark); if (mark == obj()->cas_set_mark(markWord::from_pointer (lock), mark)){ return; } // ... 后续代码省略 ... } // ... 后续代码省略 ... } ``` 这里只要先看 `if` 部分代码即可,`ObjectSynchronizer::enter` 方法对应上面字节码中的 `monitorenter` 命令,现在逐行来看加锁过程 + `mark.is_neutral()` 用来确定 **对象未被锁定(无锁状态)** + `lock->set_displaced_header(mark)`:保存原始 Mark Word,`lock` 是当前线程栈上分配的 **锁记录(Lock Record)**, `set_displaced_header` 将 mark 设置到 **锁记录** 中 **轻量级锁记录** 源码在同目录下 `basicLock` 中 ```cpp class BasicLock { // ... 前面代码省略 ... private: volatile markWord _displaced_header; // ... 后续代码省略 ... } class BasicObjectLock { // ... 前面代码省略 ... private: BasicLock _lock; // the lock, must be double word aligned oop _obj; // object holds the lock; // ... 后续代码省略 ... }; ``` 锁记录中主要存储了两个重要信息 + `BasicLock _lock` 用于保存 **锁对象的原始 Mark Word** + `oop _obj` 用于指向 **加锁对象** 再回到轻量级锁加锁源码中,`obj()->cas_set_mark(markWord::from_pointer(lock), mark)` 中 **原子交换** 当前对象 Mark Word 和 锁记录指针进行交换 + `from_pointer(lock)` 获取轻量级锁的 **地址指针** + `obj()->cas_set_mark` 是一个 **CAS(Compare-And-Sweep)**操作,简化成 `obj->cas(new, old)`,CAS 操作保证了操作的 **原子性** - 比较 old 值和 obj->mark 值是否相同,都是原始对象 Mark Word - new 值是轻量级锁记录 **地址指针 + 00**(轻量级锁 Mark Word 标识) - new 和 old 值进行交换,解锁的时候能根据地址解锁 + lock 对象 Mark Word 最后两位变成 `00`,轻量级锁加成功 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/3vRPCNCxyiMG3x6y.webp) ###### 3)重入锁 当前对象已经加了轻量级锁,并且是自己再次加锁。允许 **同一个线程** 多次获取 **同一把锁**,这种锁被称为「**可重入的(reentrant)**」 ```java new Thread(() -> { synchronized (lock) { synchronized (lock) { } } }).start(); ``` 对于已经 **持有轻量级锁** 的当前线程再次加锁,往栈桢中添加 **对象存储 null 的锁记录**,源码也很好理解 ```java else if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) { assert(lock != mark.locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark().value(), "don't relock with same BasicLock"); lock->set_displaced_header(markWord::from_pointer(NULL)); return; } ``` `if` 条件判断当前是重入轻量级锁,添加锁记录代码为 `lock->set_displaced_header(markWord::from_pointer(NULL));`,往栈桢中添加存储为 NULL 锁记录 ###### 3)解锁步骤 源码中解锁方法为 `ObjectSynchronizer::exit`,对应字节码中的 `monitorexit` 命令,下面是轻量级锁的解锁部分源码 ```c void ObjectSynchronizer::exit(oop object, BasicLock*lock, JavaThread*current) { markWord mark = object -> mark(); markWord dhw = lock -> displaced_header(); // 判断是否为重入的锁 if (dhw.value() == 0) { return; } // 轻量级锁解锁流程 if (mark == markWord::from_pointer (lock)){ if (object -> cas_set_mark(dhw, mark) == mark) { return; } } // 省略后续代码... } ``` 首先先获取加 **锁对象的 Mark Word** 和 **锁记录头中存储的信息**,这里对象信息已经交换,`mark` 存储的是 锁记录地址指针,`dhw` 存储原始对象 Mark Word `dhw.value() == 0` 上面提到,如果是同一个线程重入的锁的锁记录头中存储的信息为 NULL,则不用执行任何操作,直接返回 当 `mark == markWord::from_pointer(lock)` 为 true 时,说明 + Mark Word 最后两位是 `00`(轻量级锁标识) + 锁被当前线程持有 > markWord::from_pointer(lock) 为 false 时说明: > > + 当前线程不持有轻量级锁 > + 当前锁对象已经成为重量级锁 > > 这是就会都会进入锁膨胀,并执行重量级锁解锁步骤 > 因此可以确认 **当前线程仍持有该对象的轻量级锁**,进入解锁流程,`object -> cas_set_mark(dhw, mark) == mark` 再把锁记录中的原始 Mark Word 通过 **CAS** 交换到锁对象的 Mark Word 中 + CAS 执行成功,成功解锁轻量级锁,对象重新变为 `无锁状态` + CAS 解锁失败,进入重量级锁解锁流程 ##### 重量级锁 如果轻量级锁加锁失败,分成下面几种情况: + 默认关闭轻量级锁加锁步骤 + CAS 竞争失败 + 其他线程已经占用轻量级锁 此时都会升级成为 **重量级锁**,进入重量级锁的加锁流程(锁膨胀),交由 **内核** 中的 `Monitor` 管理 ```c // An async deflation can race after the inflate() call and before // enter() can make the ObjectMonitor busy. enter() returns false if // we have lost the race to async deflation and we simply try again. while (true) { ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_monitor_enter); if (monitor->enter(current)) { return; } } ``` ###### 1)Moniter 对象 当前有多个线程进行竞争时,就会触发 **锁膨胀**,从轻量级锁升级为 **重量级锁**,重量级锁会由 **Moniter** 对象进行管理 ```java System.out.println("锁创建时 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t\t锁状态:" + MarkWordUtils.getLock(lock)); Thread t1 = new Thread(() -> { synchronized (lock) { TimeUnit.MICROSECONDS.sleep(1); System.out.println("t1 占用锁 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); } }); Thread t2 = new Thread(() -> { synchronized (lock) { System.out.println("t2 占用锁 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); } }); // 启动线程,阻塞等待,省略代码 System.out.println("锁全部释放 Mark Word: " + MarkWordUtils.getMarkWordBinary(lock) + "\t锁状态:" + MarkWordUtils.getLock(lock)); ``` 这时两个对象发生争抢,`lock` 对象的 Mark Word 最后两位为 `10`,锁状态为 `重量级锁` ![](https://pic.code-nav.cn/post_picture/1621031759393169410/cn6bspo5UlJ9WMdw.webp) 这里锁释放了不会恢复 `无锁状态`,依旧会处于重量级锁状态 `Monitor` 对象负责管理重量级锁,主要由三部分组成:**WaitSet**,EntryList 和 **Owner** ![](https://pic.code-nav.cn/post_picture/1621031759393169410/rOaAdJFYIdZTYH7f.webp) + **Owner**:当前持有线程,唯一能执行同步代码块,唯一能调用 `wait()` 方法 + **EntryList**:「想要锁但还没得到」的线程队列,线程状态为 `BLOCKED` + **WaitSet**:「主动放弃 Owner,等待条件」的线程(调用 `wait()` 方法),等待 `notify` 唤醒 ###### 2)锁膨胀 锁膨胀总共分为 4 种情况 + **Inflated**:已经膨胀完毕 + **Stack-locked**:从轻量级锁开始膨胀 + **Inflating**:膨胀中 + **Neutral**:从无锁状态开始膨胀 ```cpp for (;;) { const markWord mark = object->mark(); assert(!mark.has_bias_pattern(), "invariant"); // The mark can be in one of the following states: // * Inflated - just return // * Stack-locked - coerce it to inflated // * INFLATING - busy wait for conversion to complete // * Neutral - aggressively inflate the object. // * BIASED - Illegal. We should never see this } ``` 锁膨胀在一个 **死循环** 中,每一次都会获取对象的 Mark Word,判断当前状态,其中从无锁对象膨胀和从轻量级锁开始膨胀逻辑相似,只介绍从轻量级锁开始膨胀 > 1. Inflated 膨胀完成 > ```c // CASE: inflated if (mark.has_monitor()) { ObjectMonitor* inf = mark.monitor(); markWord dmw = inf->header(); return inf; } ``` 如果当前已经是重量级锁,直接获取 `Moniter` 对象返回 > 2. Stack-lock 从轻量级锁开始膨胀 > ```c if (mark.has_locker()) { // 1. 创建 moniter 对象 ObjectMonitor* m = new ObjectMonitor(object); // 2. 将当前对象头设置为全0,为膨胀中标识 markWord cmp = object->cas_set_mark(markWord::INFLATING(), mark); // 3. CAS 失败,删除 moniter 对象 if (cmp != mark) { delete m; continue; // Interference -- just retry } // 4.1 获取原始 mark word 值 markWord dmw = mark.displaced_mark_helper(); // 4.2 将原始 Mark word 存入 moniter 中 m->set_header(dmw); // 4.3 设置 mointer 对持锁线程 m->set_owner_from(NULL, mark.locker()); // 4.4 锁对象头设置新的 mark word object->release_set_mark(markWord::encode(m)); _in_use_list.add(m); return m; } ``` `mark.has_locker()` 用来判断当前对象是否为轻量级锁,创建一个新的 `moniter` 对象,通过 CAS 并将当前对象 mark word 全部置为 0,标志 **膨胀中** 状态,关键是下面设置重量级锁的 4 个步骤 1. `mark.displaced_mark_helper()` 获取原始的 **mark word** 的值 2. `m->set_header(dmw)` 把原始 mark word 值存储在 `moniter` 中 3. `m->set_owner_from(NULL, mark.locker());` 设置 `moniter` 的 **Owner**,即当前持重量级锁的线程 4. `object->release_set_mark(markWord::encode(m))` 将 **monitor 的地址** encode 编码 + `10` 存入锁对象 mark word 中 经过上面操作,会保证只有一个线程针对 `lock` 锁对象加 **重量级锁** 成功 > 3. inflating 膨胀中 > 当前处于膨胀中的状态代码很好理解,`read_stable_mark` 方法等待一会 ```cpp if (mark == markWord::INFLATING()) { read_stable_mark(object); continue; } ``` 所有处于膨胀中的线程都是上一步 CAS 加锁竞争失败的,会等待真正加锁的线程加锁成功,在这里 **循环等待** ###### 3)误区:轻量级锁真的有自旋吗? 网上有些说法说 **轻量级锁** 膨胀为 **重量级锁** 时会去尝试自旋等待轻量级锁释放,自旋 10 次失败升级为重量级锁 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/48jAwsuSvkQuM2fD.webp) 甚至问 AI 有时也会得到这样的结果,还有一个合理的理由:避免升级成重量级锁的性能开销。但实际上却是如此吗? **这个说法完全是错误的**。上面关于轻量级锁膨胀为重量级锁的源码已经剖析过了,当有线程发生竞争的时候,就会立刻执行锁膨胀流程,不会去自旋 10 次。而上面唯一有循环等待的过程只有当线程处于 **膨胀中 **时会去循环等待加锁线程成功 ###### 4)重量级锁自旋 当对象已经升级成为 **重量级锁** 后,其他线程再来争抢重量级锁失败后,不会直接进入 Moniter 对象中的 EntryList,也就是并不会直接进入 `BLOCKED` 堵塞状态,而会选择 **自旋** 的方式尝试等待获取 **重量级锁** 重量级锁自旋获取锁的逻辑在源码中为 `ObjectMonitor::TrySpin` 方法,其中自旋方式分为: + 固定自旋 1000 次(jdk 1.6 后被禁用) + 自适应自旋 这里源码过长,挑重要的部分片段来解释 **自适应自旋** 获取锁的逻辑,与自适应自旋相关参数 ```c // 自旋次数上限 int ObjectMonitor::Knob_SpinLimit = 5000; // 成功奖励 static int Knob_Bonus = 100; // 失败惩罚 static int Knob_Penalty = 200; // 自旋次数下限 static int Knob_Poverty = 1000; // 预自旋次数 static int Knob_PreSpin = 10; ``` 首先会进入 **预自旋** 流程,自旋 **11** 次 ```cpp for (ctr = Knob_PreSpin + 1; --ctr >= 0;) { // 进行自旋获取锁... } ``` + 自旋成功获取锁,直接返回 + 自旋失败,进入下面 **完整自旋** 进入 **完整自旋** 逻辑,当前自旋次数由全局变量 `_SpinDuration` (初始 5000 次)记录 + 自旋失败,下次 `-200` 次,最少减为 `0`(0 表示下次完成预自旋直接进入阻塞) + 自旋成功,下次 `+200` 次,至少加为 `1000`(1000 值是指从 0 开始累加的话,直接从 1000 开始累加) 当自旋全部失败后,失败线程就会进入重量级锁加锁流程 ###### 6)_cxq 队列 这里的加锁流程是指当前 Owner 已经被占用,其他线程的「挣扎过程」,当失败线程执行完自旋获取锁的流程后,会进入 `ObjectMonitor::EnterI` 方法 1. 线程使用 `TATAS (Test-And-Test-And-Set)` 尝试 **快速获取锁**,即使上面 5000 次自旋失败了,线程还会努力 **挣扎一次**,如果成功了,直接将 Owner 设置为当前线程,直接返回 ```c // Try the lock - TATAS if (TryLock (current) > 0) { // ... return; } ``` 2. 再次进入 **自适应自旋尝试**,因为上一次失败了次数 `-200`,如果上次自旋次数是 5000 次,那这次会自旋 4800 次 ```c if (TrySpin(current) > 0) { return; } ``` 3. 自旋还是失败后,会讲当前线程加入 `Monitor` 对象中的 **_cxq 栈** 中 ```c ObjectWaiter node(current); node.TState = ObjectWaiter::TS_CXQ; // CAS操作 将当前线程加入_cxq队列 for (;;) { node._next = nxt = _cxq; if (Atomic::cmpxchg(&_cxq, nxt, &node) == nxt) break; // 如果CAS失败,重试获取锁 if (TryLock(current) > 0) return; } ``` 创建 `ObjectWaiter` 节点表示当前线程,压入栈(**LIFO 后进先出**),并设置一个 **责任线程** ```c if (nxt == NULL && _EntryList == NULL) { Atomic::replace_if_null(&_Responsible, current); } ``` 如果没有责任线程且队列为空,设置当前线程为 **责任线程**。所有加入 _cxq 栈中的线程都会使用 `park()` 命令让自己 `阻塞` 4. 设置责任线程定期唤醒,非责任线程一直阻塞 ```c for (;;) { if (TryLock(current) > 0) break; // 责任线程使用定时park,非责任线程无限期park if (_Responsible == current) { current->_ParkEvent->park((jlong)recheckInterval); recheckInterval = MIN2(recheckInterval*8, MAX_RECHECK_INTERVAL); } else { current->_ParkEvent->park(); } // 唤醒后再次尝试获取锁 if (TryLock(current) > 0) break; } ``` 通过设置责任线程的机制,能够防止线程永久挂起,并且节省了性能的开销 ###### 7)解锁步骤 + 若当前线程为轻量级锁,但调用的是 `ObjectMonitor::exit` 重锁解锁方法,会先加重锁,再解锁 + 若为重入锁(重量级锁重入的时候会对 `Monitor` 中的 `_recursions` 累加),则计数 `-1` 直接返回 重量级锁正常解锁过程,首先讲 **Owner** 设置为 NULL,顺序执行下来会出现 4 中不同情况 1. **_cxq** 和 **EntryList** 全部为空,直接返回 ```c // 没有等待线程,直接返回 if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0) { return; } ``` 2. 若 EntryList 为空,_cxq 有数据,则将 _cxq 列表全部转移到 EntryList 中 ```c for (;;) { assert(w != NULL, "Invariant"); ObjectWaiter* u = Atomic::cmpxchg(&_cxq, w, (ObjectWaiter*)NULL); if (u == w) break; w = u; } ``` 通过使用 `Atomic::cmpxchg` 保证队列转移的原子性 ```c 转换前 _cxq (LIFO 单向链表): +---+ +---+ +---+ | C |--->| B |--->| A |---> NULL +---+ +---+ +---+ 转换步骤: 1. 断开 _cxq 链接 2. 遍历链表建立双向链接 转换后 _EntryList (FIFO 双向链表): +---+ +---+ +---+ NULL <--- | A | <->| B | <->| C | ---> NULL +---+ +---+ +---+ ^ | _EntryList 指向这里 ``` 3. 当前 **EntryList** 不为空,从队列获取 **头节点**,`unpark()` 唤醒,返回 ```c w = _EntryList; if (w != NULL) { ExitEpilog(current, w); return; } ``` `w` 是 **EntryList** 的头结点,这里调用 ExitPilog 方法来唤醒 `w` 线程,主要有四个步骤 ```c // 1. 设置继承者 _succ = Wakee->_thread; // 2. 释放锁 release_clear_owner(current); // 3. 内存屏障保证操作顺序 OrderAccess::fence(); // 4. 唤醒线程 Trigger->unpark(); ``` `OrderAccess::fence()` 是一个 **全内存屏障(full memory barrier)**,用于保证了 2 步骤一定先于 4 步骤执行,即肯定先释放锁再唤醒线程 最后这里用一张流程图来简单梳理 **重量级锁的解锁流程** ![](https://ice-man-1316749988.cos.ap-shanghai.myqcloud.com/imgs/f17a91234eb03845628fcf89f07cb791.svg) ###### 8)误区:锁释放,会从 EntryList 中随机挑选一个线程唤醒吗? 很多八股文都会说当 Owner 释放了重量级锁后,会随机从 EntryList 中挑选一个线程唤醒获得锁,这的是这样吗? **结论显然是错误的**,我们上面已经剖析过源码,竞争的线程会加入 _cxq 栈中的(LIFO,先进后出),然后再将 _cxq 内容加入 EntryList 队列中(FIFO,先进先出),因此唤醒顺序一定是确认的,下面的示例代码将证明这一点 ```java final Object lock = new Object(); new Thread(() -> { synchronized (lock) { try { System.out.println("t0 加锁成功"); System.in.read(); // 阻塞 t0 } catch (IOException e) { throw new RuntimeException(e); } } }, "t0").start(); TimeUnit.MICROSECONDS.sleep(100); new Thread(() -> { synchronized (lock) { System.out.println("t1 获取锁"); } }, "t1").start(); TimeUnit.MICROSECONDS.sleep(100); new Thread(() -> { synchronized (lock) { System.out.println("t2 获取锁"); } }, "t2").start(); TimeUnit.MICROSECONDS.sleep(100); new Thread(() -> { synchronized (lock) { System.out.println("t3 获取锁"); } }, "t3").start(); ``` `t0` 线程优先获取锁,并阻塞,让 t1,t2,t3 先后加锁,最后查看获取锁的先后情况 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/ecZmRCgJEplA5FAD.webp) 最后执行结果说明了顺序一定是如下图所示,EntryList 是一个 **先进先出** 的队列,t3 最先被唤醒,并非网上说的随机挑选一个线程获取锁 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/tBqAP5ALykn5MYoc.webp) ###### 9)为什么有了 EntryList 还需要 _cxq 逻辑上,我们只需要一个队列就可以管理阻塞的线程,然后唤醒。但实现上,我们把这个队列分成了两部分:**_cxq** 和 **EntryList**,主要为了避免 **ABA 问题** 和 **惊群效应**,以及 **兼顾了吞度量和公平性** > 1. **ABA 问题** > **ABA 问题** 是并发编程中的一个经典问题,主要出现在特别是在使用 **CAS(Compare-And-Swap)** 操作时可能发生 对于共享变量 x = a, 执行 cas(a, b) 是原子性的将 x 的值交换成 b,并且会把交换的旧值返回 + 当前 x 为 a,则交换成功,x = b + 当前 x 被其他线程修改成了 c,则交换失败,本线程需要重试 ![](https://pic.code-nav.cn/post_picture/1621031759393169410/yqlsRXBXaDZKnpKg.webp) 当 CAS 操作结合 **栈** 时,就会引发 ABA 问题:,例如下面的示例 ![](https://ice-man-1316749988.cos.ap-shanghai.myqcloud.com/imgs/319053ae4e5657dea5a906181b377166.svg) 这个图已经基本反映了 **ABA 问题** 的核心逻辑:一个线程在用 CAS 操作时,只判断了“值有没有变化”,没考虑“值是否曾经被改过” + **_cxq** 是 LIFO(栈)结构,插入简单,不容易出现 ABA + 只有 JVM 自己在合适时机把 **_cxq** 中的线程“批量转移”到 **EntryList** → 所以可以集中加锁、避免高频竞 → 避免 ABA 问题 ![](https://ice-man-1316749988.cos.ap-shanghai.myqcloud.com/imgs/a7eaa0e76d232c6de79b7dcd8ba1addf.svg) > 2. **惊群效应** > **惊群效应** 指的是当 **一个资源释放时,所有等待的线程都被唤醒并竞争**,但最终只有一个线程能成功获取锁,其他线程又得重新挂起,导致 **大量无意义的上下文切换和CPU争抢** 通过 `_cxq` + `EntryList` 每次只唤醒一个线程(EntryList) 减少竞争,提高吞吐量 > 3. **兼顾吞吐量和公平性** > `_cxq` 相当于缓存了最近竞争的线程,提高了整体的 **吞吐量** `EntryList` 采用 **FIFO** 顺序可以一定的 **公平性**,防止线程 **饿死**(长时间获取不到锁)

遇到一个小并发问题

#### 场景: 并发环境下,审批业务方法中含有更新数据和其他业务处理,受同一事务管理,尽管用了乐观锁(带状态更新),两个线程同时获得了相同状态得数据,所以乐观锁避免不了。并且由于事务未提交,两个线程同时更新同一条数据(带条件->行级锁)会导致更新数据的表发生死锁 ### 解决办法: #### 从锁方面下手: 使用RetreenLock可重入锁,对公共业务方法通过业务id方式获取锁(因为是公共方法,不能直接对方法加锁,只是不允许对同一条数据同时更新,但是可以对同一张表的不同数据进行更新),将业务id作为key,锁对象作为value,如果一个线程进来,查看是否创建锁,如果没有则新创建一把锁,如果已经创建了则获取创建过的锁,在获取锁的过程中,如果锁已经释放,则获取锁,如果没有释放,则说明另外的线程在占用,直接抛出异常并给出友好提示,这里不建议设置超时时间或重试,因为从业务角度上是不允许重复对一条数据做更新的,如果设置超时重试,如果锁释放被第二个线程获取到了,会对业务和性能造成不必要的影响。 #### 从业务角度下手: 直接捕获死锁异常,抛出异常提示,但是得补货详细异常并给出友好提示。 #### 从事务下手: 因为该业务方法是在工作流当中,不确定事务管理分割情况,不建议从事务处理角度出发

并发编程基本设计方案 线程本地存储模式(没有共享,就没有伤害)

<html> <head></head> <body> <div class="content ql-editor"> <p><br></p> <p>上篇我们提到,并发编程中,并发问题之所以会产生,是因为多个线程对一个资源进行读写操作,即<strong>共享 + 读写,</strong>在上篇中,我们了解到不可变模式通过破坏读写这个条件,避免的并发问题的产生,那么今天我们要讲的是通过<strong>限制共享</strong>,来避免并发问题的产生。</p> <p><br></p> <p>在讲之前,我们先来补一补JVM的知识,我们知道JVM有三大子系统,类加载器,字节码执行引擎,以及运行时数据区,而今天我们来了解一下<strong>运行时数据区</strong>,我们从线程的视角来看看运行时数据区究竟有什么?在线程的眼中,运行时数据区,可以被分为两块区域,一块是我自己独享的,一块是我和其他线程共享的,独享的区域包括 虚拟机栈,本地方法栈,程序计数器,共享的区域包括 方法区(元数据区)堆内存。简单介绍一下这些区域的作用,方法的调用需要借助栈这种数据结构,Java这门编程语言是可以借助一些机制调用本地方法,就是Native Method,就是用C、C++写的函数,于是栈就被分为了两个,虚拟机栈主要就是用于维护Java方法的调用关系,而本地方法栈就是维护Native Method的调用关系,程序计数器就是记录当前执行到整个流程中的哪一步了。方法区(元数据区)记录了类的元信息,比如类的名字,类的字段,类的方法,堆内存就是存储对象实例的地方。</p> <p><br></p> <p>好,知识补充到此为止,到此,你已经知道了,一个线程的创建,必然会伴随着虚拟机栈,本地方法栈以及程序计数器的分配,而并发问题产生的一个条件就是,多个线程在共享一块数据,就是同一块内存区域,那么今天所说的限制共享,就是将数据移动到线程的独享区中,这样其他线程就无法访问了,从而避免的并发问题的产生。线程是执行方法的直接载体,将数据移动到线程的独享区,对应到代码中,就是将数据移入到局部变量中,这样其他线程就访问不到,限制了共享。但是这种方法存在比较严重的问题,那就是对象的重复创建,比如,有10个线程,调用A方法3次,A方法中利用局部变量保存一个对象,随着线程的执行,对象就是被重复创建,创建的个数与调用次数成正比,这对于JVM来说,是难以接受的。于是我们可以放开一点限制,数据不一定要保存在局部变量中,我们的核心目的是<strong style="font-family: inherit; font-size: inherit;font-style;font-variant-ligatures;font-variant-caps;">在线程层面,数据独享</strong>,为了达到这个目的,Java给我们提供了一个ThreadLocal ,这个工具类可以帮我们把数据维护成线程独享。但是值得注意的是,ThreadLocal仅仅只是一个工具类,并不负责数据的持有,真正持有数据的其实是Thread</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> T <span class="ql-token hljs-title">get()</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//这里就可以证明,数据其实是被Thread持有</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">Thread</span> <span class="ql-token hljs-variable">t</span> <span class="ql-token hljs-operator">=</span> Thread.currentThread(); </div> <div class="ql-code-block"> <span class="ql-token hljs-type">ThreadLocalMap</span> <span class="ql-token hljs-variable">map</span> <span class="ql-token hljs-operator">=</span> getMap(t); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (map != <span class="ql-token hljs-literal">null</span>) { </div> <div class="ql-code-block"> ThreadLocalMap.<span class="ql-token hljs-type">Entry</span> <span class="ql-token hljs-variable">e</span> <span class="ql-token hljs-operator">=</span> map.getEntry(<span class="ql-token hljs-built_in">this</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (e != <span class="ql-token hljs-literal">null</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-meta">@SuppressWarnings("unchecked")</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">T</span> <span class="ql-token hljs-variable">result</span> <span class="ql-token hljs-operator">=</span> (T)e.value; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> result; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> setInitialValue(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><br> </div> <div class="ql-code-block"> ThreadLocalMap <span class="ql-token hljs-title">getMap(Thread t)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> t.threadLocals; </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>对于ThreadLocal具体怎么用,网上已经有很多教程了,你也可以尝试看看源码,至于ThreadLocal的缺点,我们下期来简单聊聊。</p> <p><br></p> </div> </body> </html>

并发编程常见设计方案 不可变模式

<html> <head></head> <body> <div class="content ql-editor"> <p><br></p> <p>并发问题的产生的原因最核心的原因就是 <strong>共享 + 读写</strong>,如果一个变量被共享,而且还有多个线程对其进行读写,那么就一定会发生并发问题。我们解决问题的思路有两种,一种是根据问题的特点制定相关的解决方案,另外一种是不让问题产生。并发问题的出现一定是,有些线程对共享变量进行读操作,有些线程对同一共享变量进行写操作。就像解决死锁问题一样,破坏死锁的条件就能解决死锁问题,那么如果我们破坏产生并发问题的条件,那么也可以<strong>避免</strong>并发产生的问题,而今天要介绍的并发设计方案:<strong>不可变模式</strong> 破坏的就是读写条件,如果多个线程只读的话,就不会有并发问题,简单来说,就是对象一旦被创建之后,就不可被修改,始终保持着初始状态。对应到Java代码中,就是类+final, 类中的属性+final 并只提供可读方法, 在Java中有很多类都是这样的设计,比如基本数据类型的包装类(Long,Integer)还有肯定会使用的String类,这些类的设计都遵守不可变模式,</p> <div class="ql-code-block-container"> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">final</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">String</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">implements</span> <span class="ql-token hljs-title">java</span>.io.Serializable, Comparable&lt;String&gt;, CharSequence { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">/** The value is used for character storage. */</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span> <span class="ql-token hljs-keyword">final</span> <span class="ql-token hljs-type">char</span> value[]; </div> </div> <p>上面是Java8的代码,可以看到String类的设计符合不可变模式,但是不可变模式不是不能有写操作的方法吗?String类可是有replace()的呀,其实<strong>不可变模式的核心只要保证对象的状态从创建开始一直到程序结束都不改变就可以了。</strong>如果你看过 replace()的源码,就知道他其实返回的是一个新的String对象</p> <div class="ql-code-block-container"> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> String <span class="ql-token hljs-title">replace(char oldChar, char newChar)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (oldChar != newChar) { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">len</span> <span class="ql-token hljs-operator">=</span> value.length; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> -<span class="ql-token hljs-number">1</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span>[] val = value; <span class="ql-token hljs-comment">/* avoid getfield opcode */</span> </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (++i &lt; len) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (val[i] == oldChar) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">break</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (i &lt; len) { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span> buf[] = <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">char</span>[len]; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//这里就发生了复制</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">j</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; j &lt; i; j++) { </div> <div class="ql-code-block"> buf[j] = val[j]; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (i &lt; len) { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">char</span> <span class="ql-token hljs-variable">c</span> <span class="ql-token hljs-operator">=</span> val[i]; </div> <div class="ql-code-block"> buf[i] = (c == oldChar) ? newChar : c; </div> <div class="ql-code-block"> i++; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">String</span>(buf, <span class="ql-token hljs-literal">true</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-built_in">this</span>; </div> <div class="ql-code-block"> } </div> </div> <p>但是不可变模式也是有缺点的,那就是会大量创建对象,导致内存的浪费,于是我们可以通过享元模式,将相同的对象缓存起来,当要继续创建相同的对象时,就不在创建,而是从缓存池中取出来就好了,Java中的基本数据类型包装类就是这样设计的,比如Long类型就会提前缓存[-128,127]的数。</p> <div class="ql-code-block-container"> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> Long <span class="ql-token hljs-title">valueOf(long l)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">final</span> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">offset</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">128</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (l &gt;= -<span class="ql-token hljs-number">128</span> &amp;&amp; l &lt;= <span class="ql-token hljs-number">127</span>) { <span class="ql-token hljs-comment">// will cache</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> LongCache.cache[(<span class="ql-token hljs-type">int</span>)l + offset]; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Long</span>(l); </div> <div class="ql-code-block"> } </div> </div> <p>String也会做缓存,只是缓存的都是String的字面量,即写死在代码里的字符串值,比如 String s = “pandaer” “pandaer”就是字面量,他就会被缓存到字符串常量池中,如果后面在代码中还出现“pandaer”的时候用的就是字符串常量池中的同一个对象。</p> <p><br></p> </div> </body> </html>

并发编程 理论篇

<html> <head></head> <body> <div class="content ql-editor"> <p><br></p> <h2>我们为什么要学习并发编程?</h2> <p><br></p> <p>我们在日常的开发中,尤其是在开发业务接口的时候,似乎并没有过多的用到并发,最多用个线程池,syschronized​ 似乎就够了,但是真的是这样吗? 如果你只是做做简单的CRUD确实是这样的, 因为原本需要你处理的并发问题,交给了框架,交给了中间件,让你可以更加关注业务。 但是这不是我们不学习并发编程的理由, 因为当业务层出现并发问题时,怎么办呢? 或者叫你对中间件(比如Tomcat,数据库连接池)进行调优的时候,你唯一能参考的就是网上的通用设置,无法针对当前场景定制化。 所以学习并发编程很有必要</p> <p><br></p> <p><br></p> <h2>我们为什么要用并发来写程序呢?</h2> <p><br></p> <p>原因很简单,提升程序的性能,疯狂压榨硬件,提升CPU的利用率以及IO的利用率,既然要提升性能,我们就需要对性能有个定量的认识,也就是要有衡量标准。 主要的衡量标准有两个维度,一个是延迟, 另一个是吞吐量, 延迟: 一个请求发出到响应的时间。 吞吐量: 单位时间内处理的请求数。 我们写并发程序的目的就是 "降低延迟,提高吞吐量", 但是落实到代码我们应该怎么做呢?</p> <p><br></p> <p><br></p> <h2>我们应该如何写一个优雅的并发程序?</h2> <p><br></p> <p>我们先想想,我们在写一个普通的程序的时候,会先干嘛?</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>理清要写的这个程序的功能</li> <li data-list="ordered"><span class="ql-ui"></span>设计这些功能,哪些是通用的,哪些又是可以借助工具类,不需要我们自己实现</li> <li data-list="ordered"><span class="ql-ui"></span>落地实现写代码的时候,又有哪些问题需要注意?</li> </ol> <p><br></p> <p>当经历了这三步,之后我们就开始写代码了。 写一个并发程序也是这样的,首先我们需要理清哪些任务可以并发,哪些任务没必要并发,这一步我叫做分工, 然后我们需要理清这些任务与任务之间的依赖关系, 这一步我叫做同步, 由于任务与任务之间,不可避免的要交换数据,也就是访问共享变量,所以我们需要注意互斥。</p> <p><br></p> <p>所以写出一个并发程序大致要解决三个核心问题</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>分工 哪些任务需要分配给线程</li> <li data-list="ordered"><span class="ql-ui"></span>同步 线程与线程之间如何协作</li> <li data-list="ordered"><span class="ql-ui"></span>互斥 保证在同一时刻,只有一个线程访问共享资源 切记一定是共享资源</li> </ol> <p><br></p> <p>而通常我们在写完一个并发程序后,还需要注意这三个方面</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>安全性 程序的正确性,程序是否按照我们的预期执行</li> <li data-list="ordered"><span class="ql-ui"></span>活跃性 程序因某个操作无法执行下去</li> <li data-list="ordered"><span class="ql-ui"></span>性能问题 通常是并发程序中串行化过于严重</li> </ol> <p><br></p> <p>影响程序安全性的原因大致有三个 可见性问题, 原子性问题, 有序性问题</p> <p><br></p> <p>之所以有可见性的问题,是因为在缓解CPU与内存之间的速度差异时,引入高速缓存而导致的。</p> <p><br></p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1628897468148121602/ouf9k7sl.jpeg" style="" width="292">​</p> <p><br></p> <p>比如上面这张图,在内存中有一个数据值为100,CPU-1读取这个值到自己的高速缓存中,然后进行运算后,将值修改为200,但是这个时候CPU-1并不会着急将这个值写回内存,而这个时候,CPU-2又从内存中读取这个值,由于CPU-1没有写回,所以导致CPU-2读取到的值也是100,这样就会导致程序出问题, 没有按照我们的预期执行。 这就是可见性问题。 所以可以看见,产生可见性的原因不仅仅时因为高速缓存还因为有多核CPU。</p> <p><br></p> <p>对于原子性问题, 产生的原因是在缓解CPU与IO之间的速度差异时,引入分时操作系统导致的。 具体而言就是高级语言的一条语句对应低级语言 (比如汇编) 的多条指令。 然后由于IO操作,导致任务切换,或者时间片用完,导致任务切换,从而导致高级语言的一条语句没有在一个时间片内执行完,从而导致原子性问题。</p> <p><br></p> <p>对于有序性问题, 产生的原因是由于编译器为了能够更好的利用缓存,将我们写的代码的顺序优化调整了一下,导致程序的顺序和我们实际的顺序不同,从而导致出现并发问题。</p> <p><br></p> <p>对于这三个问题,Java给出了自己的答案, Java内存模型 + volatile ​解决可见性和有序性问题, 互斥锁(synchronized​)解决原子性问题。 其中volatile​关键字保证了数据的可见性。 而Java内存模型中的Happen Before原则, 指出了在哪些场景下必须保证可见性,约束了Java编译的优化,解决了有序性问题。</p> <p><br></p> <p>对于我们而言比较重要的6个"Happen Before"原则</p> <p><br></p> <p>"Happen Before": 一个操作的结果对于另一个操作是可见的</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>在同一个线程中, 上一个操作的结果,对于后续操作都是可见的。 切记一定是在一个线程内部。</li> <li data-list="ordered"><span class="ql-ui"></span>针对volatile​关键字修饰的变量, 对这个变量的写操作,对于后续这个变量的读操作是可见的。 切记这个没有必须要在同一个线程中。</li> <li data-list="ordered"><span class="ql-ui"></span>传递性原则: 如果 操作A Happen Before B, B Happen Before C, 那么 A Happen Before C。 这个规则是整个Happen Before原则中最重要的,串联这些原则的一个枢纽。</li> <li data-list="ordered"><span class="ql-ui"></span>管程中锁的规则: 对于锁的解锁操作 Happen Before 锁的加锁操作</li> <li data-list="ordered"><span class="ql-ui"></span>管程在Java中其实就是 synchronized​</li> <li data-list="ordered"><span class="ql-ui"></span>线程start()的规则: start()操作的可见性,对于启动的子线程是可见的。</li> <li data-list="ordered"><span class="ql-ui"></span>主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作</li> <li data-list="ordered"><span class="ql-ui"></span>线程join()的规则: 子线程中的操作都对join()这个操作可见</li> <li data-list="ordered"><span class="ql-ui"></span>主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B的 join() 方法实现),当子线程 B 完成后(主线程 A 中join() 方法返回),主线程能够看到子线程的操作 当然所谓的“看到”,指的是对共享变量的操作</li> </ol> <p><br></p> <p>在分析并发程序中的BUG的时候,就可以利用这些原则。</p> <p><br></p> <p>解决了可见性以及有序性问题之后, Java解决原子性问题的方案就是我们都熟悉的锁模型,也就是我们都知道的synchronized​关键字。 但是其实Java解决原子性问题背后的真正理论模型是管程, 管程是一种基本的同步原语, 具体而言就是将共享资源保护起来,提供访问资源的方法。 管程有三个模型, Hasen 模型、Hoare 模型和 MESA 模型,而Java实现的就是MESA 模型,只不过做了一点简化。 接下来我们来看看MESA 模型的运行原理</p> <p><br></p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1628897468148121602/yqkvm4xo.jpeg" style="" width="624">​</p> <p><br></p> <p>当一个线程A,获取锁,进入管程内部后,要去操作共享变量,如果进行这个操作需要满足条件1, 然后他就会检查是否满足条件1,如果检查到不满足,就会释放锁,进入条件变量1的等待队列中,这个时候入口等待队列中的线程开始重新竞争锁,假设线程B 获取到锁进入线程内部,进行一些操作,这次需要检查条件2,于是线程B就会检查条件2是否满足,如果不满足,同样会进入条件变量2的等待队列中。 当某个时刻,条件1满足时, 会唤醒条件1等待队列中的线程,然后这些线程就会进入到入口等待队列中,等待锁资源并去竞争。 同理,如果条件2满足, 也会唤醒在条件变量2等待队列中的线程,这些线程也会进入到入口等待队列,然后去竞争锁资源。</p> <p><br></p> <p>理解了管程模型的运行原理之后,接下来我们来看看Java中对管程的实现 —— synchronized代码块 Java在对其实现的过程中,对其进行了一定的简化,比如锁资源和条件变量绑定,即一个锁资源对应一个条件变量,我们都知道一个synchronized代码块只能上一把锁,所以在代码块中可供操作的条件变量也只有一个,而且还是锁本身。而Java提供的操作就是wait()​ notify()​ notifyAll()​ wait()​的作用就是将需要等待资源的线程放入到条件等待队列中。notify()​ notifyAll()​ 对应的就是唤醒操作了</p> <p><br></p> <p>现在我们知道了并发程序的安全性问题主要是可见性问题,原子性问题,以及有序性问题,可是落实到代码上,有没有什么样的代码形式会出现这三类问题呢?毕竟解决问题的第一步就是识别问题嘛,好在前人都帮我们总结好了。</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>要出现并发安全问题,那么一定是多个线程访问一块内存,对应到代码:在多个线程中访问了一个共享变量。所以就有人评价说,没有共享,就没有伤害 虽然有点滑稽,但是他是解决并发问题的终极方案 —— 不共享</li> <li data-list="ordered"><span class="ql-ui"></span>如果多个线程对一个共享变量进行读写操作,就会出现并发安全性问题,由于这种操作太常见了,所以人们给这种情况起了一个名字:数据竞争。</li> <li data-list="ordered"><span class="ql-ui"></span>如果一个程序的执行,依赖一个状态,而这个状态又是共享的,也会引发并发安全性问题,这个情况也过于常见了,所以人们给这种操作去了一个名字:竞态条件</li> </ol> <p><br></p> <p>总的来说,只要存在多个线程之间共享变量,就会存在并发问题,前提是对这个共线变量有写操作。</p> <p><br></p> <p>聊完了并发程序中的安全性问题,接下来我们来聊聊活跃性问题,所谓的活跃性问题具体而言就是程序因为某些情况无法执行下去了,相信大家能想到的情况就是死锁,其实除了死锁,还有“活锁”,“饥饿”这些问题也会导致程序无法执行下去。</p> <p><br></p> <p>我们先来聊聊,大家都比较熟悉的死锁问题,如果程序一旦发生死锁,一般情况下,是没有什么好的解决办法,只能重启程序,所以解决死锁的方案只剩下一个了,那就是避免死锁,那么只要我们知道死锁发生的条件,在写程序的时候,刻意避免,就可以了。那么死锁产生的条件究竟有哪些呢?别怕,前人已经总结好了,发生死锁必须同时满足四个条件</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>互斥 共享资源X和Y只能被一个线程占用,具体而言:一个操作包含两个共享资源</li> <li data-list="ordered"><span class="ql-ui"></span>占用且等待 线程1占用资源X,等待获取资源Y,但是不释放资源X</li> <li data-list="ordered"><span class="ql-ui"></span>不可抢占 其他线程不能强行抢占线程1占用的资源</li> <li data-list="ordered"><span class="ql-ui"></span>循环等待 线程1占用资源X,等待资源Y,线程2占用资源Y,等待资源X</li> </ol> <p><br></p> <p>只有这四个条件同时满足,才会产生死锁,换句话说如果有一个条件不满足,那么就不会发生死锁,所以我们只需要破坏一个条件即可。</p> <p><br></p> <p>针对互斥,这个条件我们必须满足,因为这个是我们写并发程序保证安全性的一个要求。</p> <p><br></p> <p>针对占用且等待,我们可以将资源X和资源Y打包,要么一起占用,要么就不占用,落实到代码,就是封装一个类,包含资源X,资源Y,然后提供两个操作,申请资源,释放资源,并保证这个类是线程安全的就好了</p> <p><br></p> <p>针对不可抢占,如果线程1占用X资源,在申请Y资源的时候,没有申请到,就释放X资源。</p> <p><br></p> <p>针对循环等待,问题产生的原因就是因为资源获取的先后顺序不一样,解决方案就是给资源进行一个排序,每个线程获取资源的时候,必须按照这个顺序获取。</p> <p><br></p> <p>死锁介绍完了,接下来就简单介绍一下“活锁”,“饥饿”问题,对于“活锁”而言,举个例子就明白了,比如在走路的时候,你前面遇到了一个人,挡住了去路,你和他都可能选择让路,如果你们都选择让路,那么就又挡道了,然后你们又让路,结果又挡道了,不过这个过程不会持续很久,因为你们会沟通,但是线程可不会沟通,所以解决办法就是随机等待一段时间之后,再让路。</p> <p><br></p> <p>“饥饿”问题,主要是由于程序中不断有优先级高的线程执行,导致优先级低的线程无法执行,这种现象叫做饥饿,这个问题可以利用公平锁来解决。</p> <p><br></p> <p>活跃性问题简单的介绍了一下,接下来就是并发程序的性能问题了,这也是我们最关心的问题,因为我们写并发程序就是为了提高性能,即“降低延迟,提高吞吐量“,产生性能问题的原因有,串行化占比太大,解决方案就是可以考虑无锁算法,以及尽量减少持有锁的时间,比如细粒度锁。</p> <p><br></p> <p>到此呢,并发程序从设计到编写,以及写好后需要考虑的问题都一一做了介绍,接下来我们还需要一点前置知识,才能写好Java中的并发程序。</p> <p><br></p> <p><br></p> <h2>Java中的线程模型</h2> <p><br></p> <p>在Java中写并发程序的手段就是多线程,所以理解Java的线程模型,有利于我们写出好的多线程程序。</p> <p><br></p> <p>说到线程模型,我们需要从操作系统中的线程模型讲起,然后再讲解Java是如何对操作系统的线程进行一定的封装简化的,最后再简单聊聊如何在Java中利用面向对象的思想写并发程序。</p> <p><br></p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1628897468148121602/62ss90oy.jpeg" style="" width="498">​</p> <p><br></p> <p>先来聊聊线程的生命周期,从代码的声明需要一个线程,到操作系统真正创建完一个线程这段时间被称为初始状态,只要线程创建完毕,它就会进入到可运行状态,等待CPU资源,当线程获取到CPU资源后,就进入运行状态,如果CPU的时间片用完,就会回到可执行状态,如果在执行的过程中,需要IO操作,这个时候线程会进入到休眠状态,当条件满足时,线程又会从休眠状态回到可执行状态,等待CPU资源。如果线程在运行状态,由于任务执行完毕,或者发送异常就会进入到终止状态。</p> <p><br></p> <p>这是操作系统的线程模型,而Java对这个模型进行一定封装。</p> <p><br></p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1628897468148121602/shvkdaix.jpeg" style="" width="544">​</p> <p><br></p> <p>对比可以发现,Java中的线程,将可运行状态和运行状态合并了,并细分了休眠状态。</p> <p><br></p> <p>那么我们就来聊聊,Java中线程的生命周期,当程序执行到 new Thread();​的时候,线程处于初始状态,当程序执行到 <a href="http://thread.start" target="_blank">thread.start</a>()​的时候,线程处于runnable状态,如果在代码中遇到 同步代码块而且还没有获取到锁资源,那么线程会进入到 blocked状态,只有锁释放的时候,线程才会从blocked​状态进入到runnable状态,如果程序中遇到有wait()​,那么线程就会进入到 waiting状态,如果程序中遇到 wait(100)​,sleep(1000)​,线程就会进入到time_waiting状态,如果任务执行完,或者执行过程中发送异常就会进入到terminated状态。</p> <p>其实进入到waiting状态的方式不止wait()​一种,只要没有时间限制的等待,不是获取锁资源的阻塞,其他的等待都会进入到waiting状态,比如thread1.join()​</p> <p>会导致执行这行代码的线程进入到waiting状态</p> <p>在描述生命周期的时候,我提到了任务这个词,在并发编程中主要由两类任务</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>IO密集型计算 IO计算远远大于CPU计算</li> <li data-list="ordered"><span class="ql-ui"></span>CPU密集型计算 CPU计算远远大于IO计算</li> </ol> <p><br></p> <p>针对不同种类的计算,我们设置的线程数也不同,线程数不是越多越好,是需要根据场景来的。CPU密集型计算,意味着该任务会一直使用CPU资源,很少使用IO,这种任务,如果我们要提高性能,只能上多核,提供更多的CPU资源,所以一般的线程数设置在理论上等于机器的CPU核数(逻辑值),因为有些CPU支持超线程技术。而针对IO密集型计算,我们需要估算CPU与IO的耗时比,然后才能判断需要多少个线程,具体的公式如下 CPU个数 * [1 + (IO耗时/CPU耗时)] 这个公式,主要是想让CPU的利用率和IO的利用率达到100%。但是这是理论值,实际还是需要压测。</p> <p><br></p> <p>到此Java的内存模型说清楚了,最后再来谈谈,如何利用面向对象的思想写并发程序。</p> <p><br></p> <p>其实利用面向对象的思想写并发程序,主要还是借鉴管程的思想。</p> <p><br></p> <ol> <li data-list="ordered"><span class="ql-ui"></span>封装共享变量</li> <li data-list="ordered"><span class="ql-ui"></span>识别共享变量之间的约束关系</li> <li data-list="ordered"><span class="ql-ui"></span>制定共享变量的访问策略</li> </ol> <p><br></p> <p>至于如何识别共享变量之间的约束关系,以及如何制定访问策略,这些内容我们下期见。</p> <p><br></p> <p><br></p> <p><br></p> </div> </body> </html>

Java并发编程第12讲——cancelAcquire()流程详解及acquire方法总结

<html> <head></head> <body> <div class="content ql-editor"> <p>​各位球友早上好😁,今天这篇文章,应该可以让你跟面试官稍微battle一下AQS的源码,整篇文章将近30张图,复制粘贴麻了😵。直接上目录(直接上截图了):</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/437nnqb0.jpeg"></p> <p><br></p> <p><strong>原文博客:</strong><a href="https://blog.csdn.net/weixin_45433817/article/details/134538055" target="_blank">Java并发编程第12讲——cancelAcquire()流程详解及acquire方法总结</a></p> <p><br></p> <p><strong style="color: rgb(236, 40, 52);">-----正文开始-----</strong></p> <p><br></p> <p>上篇文章介绍了AQS的设计思想以及独占式获取和释放同步状态的源码分析,但是还不够,一是感觉有点零零散散,二是里面还有很多细节没介绍到——比如<span style="color: rgb(254, 44, 36);">cancelAcquire()方法</span>(重点),迫于篇幅原因,今天就把它放到这篇文章里,继续深入AQS!</p> <p><br></p> <h1>一、acquire方法</h1> <p><br></p> <p>源码的分析在上一篇文章,感兴趣的同学可以去看一下,我的建议是两篇文章一起看。</p> <p><br></p> <h2>1.1 几个状态(重点)</h2> <p><br></p> <p><span style="color: rgb(149, 111, 231);">ps:waitStatus&gt;0说明等待状态时CANCELLED,waitStatus&lt;0为其它状态。</span></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-comment">//表示线程已取消:由于在同步队列中等待的线程等待超时或中断//需要从同步队列中取消等待,节点进入该状态将不会变化(即要移除/跳过的节点)static final int CANCELLED = 1;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//表示后继节点处于park,需要唤醒:后继节点的线程处于park,而当前节点//的线程如果进行释放或者被取消,将会通知(signal)后继节点。static final int SIGNAL = -1;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//表示线程正在等待状态:即节点在等待队列中,节点线程在Condition上,//当其他线程对Condition调用signal方法后,该节点会从条件队列中转移到同步队列中static final int CONDITION = -2;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//表示下一次共享模式同步状态会无条件地传播下去static final int PROPAGATE = -3;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//节点的等待状态,即上面的CANCELLED/SIGNAL/CONDITION/PROPAGATE,初始值为0volatile int waitStatus;</span> </div> </div> <p><br></p> <h2>1.2 acquire()流程图及分析</h2> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/p9gl3tdk.jpeg"></p> <p><br></p> <p>基本流程描述:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>调用子类重写的<span style="color: rgb(254, 44, 36);">tryAcquire方法</span>尝试获取同步状态,若成功则返回,反之进入<span style="color: rgb(254, 44, 36);">addWaiter方法</span>。</li> <li data-list="bullet"><span class="ql-ui"></span>基于当前线程新建一个Node节点,若队列不为空则将Node节点CAS操作挂在队列尾部,队列为空或CAS失败进入<span style="color: rgb(254, 44, 36);">enq方法</span>。</li> <li data-list="bullet"><span class="ql-ui"></span>查看队列是否已经初始化,若没有则优先初始化队列(自旋),随后将Node节点以CAS的方式插入队列,CAS失败则继续自旋,反之进入<span style="color: rgb(254, 44, 36);">acquireQueued方法</span>。</li> <li data-list="bullet"><span class="ql-ui"></span>若Node的前驱节点为头节点,且再次tryAcquire()成功,则将Node设置为头节点,并结束自旋。若两个条件任意一个失败则进入<span style="color: rgb(254, 44, 36);">shuoldParkAfterFailedAcquire方法</span>。</li> <li data-list="bullet"><span class="ql-ui"></span>若Node的前驱节点等待状态为SIGNAL,则调用<span style="color: rgb(254, 44, 36);">parkAndCheckInterrupt方法</span>将当前线程阻塞,若当前线程的中断状态为ture则将<span style="color: rgb(254, 44, 36);">acquireQueued</span>的返回值置为true,并继续自旋;反之则判断Node的前驱节点的等待状态是否为CANCELLED,若不是则CAS尝试将Node的前驱节点等待状态改为SIGNAL,并继续自旋;若是则说明这个前驱节点无效,直接跳过该节点并找一个非CANCELLED节点作为Node的前驱节点,并结束自旋(<span style="color: rgb(254, 44, 36);">acquireQueued方法</span>结束)。</li> <li data-list="bullet"><span class="ql-ui"></span>若<span style="color: rgb(254, 44, 36);">acquireQueued方法</span>返回ture则说明当前线程需要被中断(也就是Node节点的前面还有节点在排队,还没轮到Node节点)。</li> <li data-list="bullet"><span class="ql-ui"></span>若在<span style="color: rgb(254, 44, 36);">acquireQueued方法</span>中出现异常,则会调用<span style="color: rgb(254, 44, 36);">cancelAcquire方法</span>进行该节点的取消逻辑,这也是我们今天的重点,下面会具体分析。</li> </ol> <p><br></p> <h1>二、cancelAcquire方法</h1> <p><br></p> <p>上篇文章在分析它的源码时就感觉有点懵懵的,很多地方都不太理解(那面试的时候怎么跟面试官battle啊😁),那么今天就深入的分析一下。</p> <p><br></p> <h2>2.1 源码</h2> <p><br></p> <p>再次附上源码,方便观看。</p> <p>在acquireQueued方法中出现异常会走cancelAcquire方法取消正在进行acquire的尝试,以防止死锁或长时间的等待。<span style="color: rgb(254, 44, 36);">这里我把它分为两个红框,下面图解流程时会用到。</span></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/rrqtecx8.jpeg"></p> <p><br></p> <p><br></p> <h2>2.2 流程图</h2> <p><br></p> <p>解释一下颜色代表的意思:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>紫色——方法开始和结束。</li> <li data-list="bullet"><span class="ql-ui"></span>橙色——断开与取消结点联系的执行逻辑。</li> <li data-list="bullet"><span class="ql-ui"></span>黄色——node结点为tail结点执行的逻辑。</li> <li data-list="bullet"><span class="ql-ui"></span>蓝色——node结点不为tail结点执行的逻辑(为head结点的后继结点或中间结点)。</li> <li data-list="bullet"><span class="ql-ui"></span>其它——一般逻辑。</li> </ol> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/yby883bg.jpeg"></p> <p><br></p> <h2>2.3 Node为尾节点</h2> <p><br></p> <p>初始状态:即N1为Node节点</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/4wmprfgu.jpeg"></p> <p><br></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>将Node结点Thread置空。</li> <li data-list="bullet"><span class="ql-ui"></span>Node节点的前驱结点N2的等待状态为CANCELLED,所以断开N1到N2的联系,并与N3建立联系。</li> <li data-list="bullet"><span class="ql-ui"></span>将pred指向N3结点,predNext指向N2(这里不是node节点)并把Node结点的等待状态置为CANCELLED。</li> </ol> <p>执行第二个红框:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/18uga0o7.jpeg"></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>将pred设置为tail节点。</li> <li data-list="bullet"><span class="ql-ui"></span>断开N3到N2结点的联系。</li> <li data-list="bullet"><span class="ql-ui"></span>最后N1和N2节点会被GC回收。</li> </ol> <p><br></p> <h2>2.4 node为中间节点</h2> <h3>2.4.1 N3节点取消流程</h3> <p><br></p> <p>初始状态:Node节点为中间节点,既不是tail节点,也不是head节点的后继节点。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/m7ggfllj.jpeg"></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>将node的Thread置为null。</li> <li data-list="bullet"><span class="ql-ui"></span>pred指向N4,preNext指向N3,也就是node节点。</li> <li data-list="bullet"><span class="ql-ui"></span>将node的等待状态置为CANCELLED。</li> </ol> <p>执行第二个红框:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/crcy4xny.jpeg"></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>next指向node节点的后继节点N2。</li> <li data-list="bullet"><span class="ql-ui"></span>CAS将N4的后继节点置为N2。</li> <li data-list="bullet"><span class="ql-ui"></span>Node的后继节点指向自己。</li> </ol> <p><strong>注意:此时N2对N3的指针还没有断开,这就意味着N2并不会被GC回收,那么N2对N3的引用为什么不断开?当时作者也有点不理解,直到...假设N2也调用了cancelAcquire方法,下面一起来看一下。</strong></p> <p><br></p> <h3>2.4.2 继N3取消后N2取消逻辑</h3> <p><br></p> <p>初始状态:N2为取消节点,这里就不作解释了。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/vgh7vk72.jpeg"></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>N2执行完取消逻辑后,N3就会被GC回收。这里我们思考一下如果N3取消逻辑执行完之后就断开N2到N3的prev指针会发生什么?很简单,N2就遍历不到它前面的结点了,所以N3在取消时保留了N2到N3的指针。</li> <li data-list="bullet"><span class="ql-ui"></span>再思考一个问题,N3被GC回收了,要是N2执行取消逻辑后,没有后继结点取消了,那N2如何被GC回收回收呢?</li> </ol> <p><br></p> <h3>2.4.3 继N2取消后N2被GC回收逻辑</h3> <p><br></p> <p>N2被GC回收其实是在N4结点成功获取同步状态且释放同步状态,并唤醒其后继节点N1时完成的,我们来看一下。</p> <p>此时的N4为head结点,N1为node结点。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/lsmxqnx8.jpeg"></p> <p>我们再来回顾下acquireQueued方法。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/pdxdrrm8.jpeg"></p> <p><strong>注意此时N1的prev还是N2,所以会执行shouldParkAfterFailedAcquire方法。</strong></p> <p><strong><br></strong><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/kc6l9iru.jpeg"></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/susk2lrj.jpeg"></p> <p><br></p> <p>至此N2也会被GC回收,T4继续自旋,直到成功获取同步状态或出现异常。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>所以取消节点被GC回收有两种情形:<strong>一是后继结点取消,二是后继结点被唤醒。</strong></li> </ol> <p><br></p> <h2>2.5 node为头节点的后继结点</h2> <h3>2.5.1 N3的取消逻辑</h3> <p><br></p> <p>初始状态:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/l830mvf5.jpeg"></p> <p>执行第二个红框:会调用unparkSuccessor方法</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/gcpajot9.jpeg"></p> <p><br></p> <p><br></p> <h3>2.5.2 继N3取消逻辑N2被唤醒</h3> <p><br></p> <p>N2被唤醒尝试获取同步状态,也就是执行acquireQueued方法。</p> <p>初始状态:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/dusizqom.jpeg"></p> <p>返回false,T2接着自旋,假设tryAcquire成功,执行setHead方法。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/vr8lnr9r.jpeg"></p> <p>接着有一段神奇的代码,将<strong>原head指向N2的next指针断开</strong>。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/ys01o3xg.jpeg"></p> <p><br></p> <p>至此,原head结点完全脱离队列,等待GC回收。但不执行p.next=null似乎也符合GC的条件,那为什么要执行呢?</p> <p>如果不执行p.next=null,垃圾回收器也能自动检测并回收,但这个过程相比较而言会更耗时。也就是如果p.next仍然引用N2,那么可能会遍历整个链表来标记垃圾,这就会花费更多的时间和资源才能发现并回收p结点。执行p.next=null可以明确地告诉垃圾回收器,与p关联的结点均为垃圾,并加速回收过程。</p> <p><br></p> <h1>三、总结</h1> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>cancelAcquire方法就负责取消结点的逻辑,即将前置结点等待状态、线程置空、非取消和后置非取消结点联系起来、或在特定场景下唤醒后继结点。</li> <li data-list="bullet"><span class="ql-ui"></span>shouldParkFailedAcquire方法的作用就是挂起线程和队列调整进而GC回收取消节点,即当前结点前驱节点的等待状态为SIGNAL时,返回true,将当前线程挂起。反之会调整队列将取消结点进行GC回收。</li> </ol> <p>还有setHead方法添加头节点(初始化队列)和删除头节点,p.next=null加速GC回收等等,每个方法甚至每段代码都配合的十分精妙,我只能说一句🐂🍺。</p> <p><strong style="color: rgb(255, 153, 0);">End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。</strong></p> <p><br></p> </div> </body> </html>

Java并发编程第11讲——AQS设计思想及核心源码分析

<html> <head></head> <body> <div class="content ql-editor"> <p>​全文12742字。</p> <p>hello球友们,大家早上好😇。前面把JUC并发包下的同步组件比如ReentrantLock、CountDownLactch、CyclicBarrier和Semaphore等都介绍的差不多了,那么今天就带来它们的基类——AbstractQueuedSynchronizer,这是真正的Java并发基石,通过它可以更好地理解同步、锁和并发控制。</p> <p>目录如下:</p> <p>一、什么是AQS:AQS的设计背景、相关概念(独占式和共享式)。</p> <p>二、AQS常用方法与示例:可重写的方法(子类重写)、常用的方法(比如获取和释放操作)、借用jdk1.8官方文档的示例利用AQS实现Mutex锁。</p> <p>三、设计思想:主要分为同步状态的原子性管理(state)、队列的管理(CLH变体队列)以及线程的阻塞和释放(LockSupport)三部分。</p> <p>四、acquire和release操作源码分析:acquire-》addWaiter-》enq-》acquireQueued-》shouldParkAfterFailedAcquire-》parkAndCheckInterrupt-》cancelAcquire-》unpakSuccessor和release等方法的分析。</p> <p><br></p> <p>原文博客:<a href="https://blog.csdn.net/weixin_45433817/article/details/134270970" target="_blank">Java并发编程第11讲——AQS设计思想及核心源码分析</a></p> <p><br></p> <p><strong style="color: rgb(204, 0, 0);">正文开始...</strong></p> <p><br></p> <p>Java并发包(JUC)中提供了很多并发工具,比如前面介绍过的<strong>ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore、FutureTask</strong>等锁或者同步部件,它们的实现都用到了一个共同的<span style="color: rgb(254, 44, 36);">基类——AbstractQueuedSynchronizer</span>,简称AQS。本篇文章将深入剖析AQS的工作原理和核心概念,以理解多线程同步的关键技术。</p> <p><br></p> <h1>一、什么是AQS</h1> <p><br></p> <p>AQS全称AbstractQueuedSynchronizer。JDK 1.5之前只有synchronized同步锁,并且效率并不高,<strong>因此并发大神Doug Lea在JDK 1.5的时候自己写了一套框架,希望能够成为高效率地实现大部分同步需求的基础,也就是我们现在熟知的AQS(队列同步器)</strong>。</p> <p><br></p> <p>AQS提供了一个同步器的框架,JUC包下大多数同步器都是围绕着AQS 使用的一组共同的基础行为(<strong>如等待队列、条件队列、独占或共享获取等</strong>)实现的,比如前边提到的ReentrantLock、CountDownLatch、Semaphore、FutureTask等,当然,我们也可以用AQS来构造出一个符合我们自己需求的同步器。</p> <p><br></p> <p>AQS支持两种同步方式:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>独占式(<span style="color: rgb(254, 44, 36);">Exclusive</span>):<strong>同一时刻只能有一个线程持有同步资源或锁。</strong>当一个线程成功获取到锁时,其它线程就必须等待,直到持有锁的线程释放资源才能继续执行,比如ReentrantLock。</li> <li data-list="bullet"><span class="ql-ui"></span>共享式(<span style="color: rgb(254, 44, 36);">Shared</span>):<strong>多个线程可以同时获取同一个同步资源或锁</strong>,从而实现并发方法。当一个线程获取到共享资源或锁后,其它线程仍然有机会获取资源,而不是被阻塞。比如CountDownLatch、Semaphore和CyclicBarrier就是一种共享锁。</li> </ol> <p><br></p> <h1><span style="background-color: transparent;">二、AQS的常用方法与示例</span></h1> <p><br></p> <p>AQS的设计是基于<a href="https://blog.csdn.net/weixin_45433817/article/details/131355105?spm=1001.2014.3001.5501" target="_blank" style="color: rgb(13, 0, 22);">模板设计模式</a>的,也就是说,使用者(<strong>子类</strong>)需要继承AQS并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并<strong>调用AQS提供的模板方法,而这些模板方法将会调用子类重写的方法。</strong></p> <p><br></p> <h2><span style="background-color: transparent;">2.1 可重写的方法</span></h2> <p><br></p> <p><strong>使用AQS的一般方式:</strong></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>继承AQS并重写指定的方法。(无非是对于共享资源state的获取和释放)</li> <li data-list="bullet"><span class="ql-ui"></span>将AQS组合在自定义同步组件中,并调用其模板方法,这些模板方法就会调用子类重写的方法,这是模板方法设计模式一个典型的应用。</li> </ol> <p><br></p> <p>需要注意的是,重写AQS指定方法的同时,需要使用同步器提供的下面三个方法来访问和修改同步状态:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>getState():获取当前同步状态。</li> <li data-list="bullet"><span class="ql-ui"></span>setState():设置当前同步状态。</li> <li data-list="bullet"><span class="ql-ui"></span>compareAndSetState(int expect,int update):使用CAS设置当前状态。</li> </ol> <p><br></p> <p>下面我们看看AQS定义的可重写的5个方法:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span><strong>protected boolean tryAcquire(int arg):</strong>独占式获取同步状态,试着获取,成功返回true,失败返回false。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>protected boolean tryRelease(int arg):</strong>独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>protected int tryAcquireShared(int arg):</strong>共享式获取同步状态,返回小于0的值表示获取失败,反之成功。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>protected boolean tryReleaseShared(int arg):</strong>共享式释放同步状态,成功true,失败false。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>protected boolean isHeldExclusively():</strong>是否在独占模式下被线程占用。</li> </ol> <p><br></p> <p>看过我之前文章的同学,除了最后一个方法,其它的是不是都很熟悉但又有点“模糊”,那么今天我们就一探究竟😊,看看到底是怎么个事。</p> <p><br></p> <h2><span style="background-color: transparent;">2.2 常用方法</span></h2> <p><br></p> <p>实现自定义同步组件时,将会调用同步器提供的模板方法,如下(部分):</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span><strong>void acquire(int arg):</strong>独占式获取同步状态,如果当前线程获取同步状态成功,则返回,否则将会进入同步队列等待,该方法会调用重写的tryAcquire(int arg)方法。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>void acquireInterruptibly(int arg):</strong>与acquire方法相同,但是该方法响应中断,若线程未获取同步状态进入到同步队列,如果当前线程中断,则会抛出InterruptedException。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>boolean tryAcquireNanos(int arg,long nanos):</strong>在acquireInterruptibly方法基础上增加了超时限制,如果超时,返回false,获取成功返回true。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>void acquireShared(int arg):</strong>共享式获取同步状态,如果为获取,将进入同步列等待,与独占式获取的主要区别在于同一时刻可以又多个线程获取到同步状态。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>boolean tryAcquireSharedInterruptibly(int arg):</strong>与acquireShared方法相同,可响应中断。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>boolean tryAcquireSharedNanos(int arg,long nanos):</strong>共享模式获取,可中断,并且有超时时间。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>boolean release(int arg):</strong>独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>boolean releaseShare(int arg):</strong>共享式获取同步状态。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>Collection&lt;Thread&gt; getQueuedThreads():</strong>获取等待在同步队列上的线程集合。</li> </ol> <p><br></p> <h2><span style="background-color: transparent;">2.3 基于AQS实现Mutex锁(示例)</span></h2> <p><br></p> <p>上面大概讲了一下AQS的使用方式和常用的一些方法,接下来就借用JDK 1.8官方文档 在介绍AQS类时,举的一个例子来进一步理解AQS。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Mutex</span> <span class="ql-token hljs-keyword">implements</span> <span class="ql-token hljs-title">Serializable</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// 静态内部类,自定义同步器private static class Sync extends AbstractQueuedSynchronizer {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// 是否处于占用状态protected boolean isHeldExclusively() {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> getState() == <span class="ql-token hljs-number">1</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// 当前状态为0的时候获取锁,CAS成功则将state修改为1public boolean tryAcquire(int acquires) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">assert</span> <span class="ql-token hljs-variable">acquires</span> <span class="ql-token hljs-operator">=</span>= <span class="ql-token hljs-number">1</span>; <span class="ql-token hljs-comment">// Otherwise unusedif (compareAndSetState(0, 1)) {</span> </div> <div class="ql-code-block"> setExclusiveOwnerThread(Thread.currentThread()); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">true</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">false</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// 释放锁,将同步状态设置为0protected boolean tryRelease(int releases) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">assert</span> <span class="ql-token hljs-variable">releases</span> <span class="ql-token hljs-operator">=</span>= <span class="ql-token hljs-number">1</span>; <span class="ql-token hljs-comment">// Otherwise unusedif (getState() == 0) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">IllegalMonitorStateException</span>(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> setExclusiveOwnerThread(<span class="ql-token hljs-literal">null</span>); </div> <div class="ql-code-block"> setState(<span class="ql-token hljs-number">0</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">true</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//同步对象完成一系列复杂的操作,我们仅需指向它即可private final Sync sync = new Sync();</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//加锁,代理到acquire(模板方法)上,acquire会调用我们重现的tryAcquire方法public void lock() {</span> </div> <div class="ql-code-block"> sync.acquire(<span class="ql-token hljs-number">1</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">tryLock()</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> sync.tryAcquire(<span class="ql-token hljs-number">1</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//释放锁,代理到release(模板方法上),release会调用我们重写的tryRealease方法public void unlock() {</span> </div> <div class="ql-code-block"> sync.release(<span class="ql-token hljs-number">1</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">isLocked()</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> sync.isHeldExclusively(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>上述示例中,独占锁Mutex是一个自定义的同步器,它在同一时刻只允许一个线程占有锁。接下来我们就是用常见的i++例子来检验一下Mutex:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">TestMutex</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-type">Mutex</span> <span class="ql-token hljs-variable">mutex</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Mutex</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//使用自定义的Mutex进行同步处理的a++public static void increase() {</span> </div> <div class="ql-code-block"> mutex.lock(); </div> <div class="ql-code-block"> i++; </div> <div class="ql-code-block"> mutex.unlock(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> Exception { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//启动十个线程,每个线程累加10000次for (int j = 0; j &lt; 10; j++) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Thread</span>(() -&gt; { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">k</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; k &lt; <span class="ql-token hljs-number">10000</span>; k++) { </div> <div class="ql-code-block"> increase(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }).start(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (Thread.activeCount()&gt;<span class="ql-token hljs-number">2</span>){ </div> <div class="ql-code-block"> Thread.yield(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> System.out.println(i);<span class="ql-token hljs-comment">//100000</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>每次测试i的结果都是预期的100000,说明我们成功地基于AQS实现了一个简单的Mutex锁。</p> <p><br></p> <h1>三、设计思想</h1> <p><br></p> <p>AQS的设计思想实际很简单,可以分为三部分:<strong>同步状态的原子性管理(state)、队列的管理(CLH)以及线程的阻塞和释放(acquire、release)</strong>,下面我们就逐个介绍一下。</p> <p><br></p> <h2>3.1 同步状态的管理(state)</h2> <p><br></p> <p>每个AQS的子类都依赖于一个volitile修饰的状态变量<span style="color: rgb(254, 44, 36);">(state)</span>,可以通过<span style="color: rgb(254, 44, 36);">getstate</span>、<span style="color: rgb(254, 44, 36);">setState</span>以及<span style="color: rgb(254, 44, 36);">compareAndSetState</span>等方法进行操作,这个变量可以用于表示任意状态,比如<strong>ReentrantLock</strong>用它表示拥有锁的线程重复获取该锁的次数,<strong>CountDownLatch</strong>用它表示计数器的数值,<strong>Semphore</strong>用它表示剩余的许可数量,<strong>FutureTask</strong>用它表示任务的状态(尚未开始、正在运行、已完成和已取消)。</p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/70cyg24h.jpeg"></p> <p><br></p> <h2>3.2 队列的管理</h2> <p><br></p> <p><strong>AQS最核心的就是队列的管理。</strong>AQS内部维护了两个内部类,分别是Node类(构建同步队列)和ConditionObject类(条件队列)。</p> <p><br></p> <h3>3.2.1 同步队列(CLH变体队列)</h3> <p><br></p> <p>AQS的核心思想就是<strong>如果被请求的共享资源(state的状态)空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制就是用CLH变体的虚拟双向队列实现的,即将暂时获取不到锁的线程加入到队列中。</strong></p> <p>我们先简单介绍下CLH队列:</p> <p><span style="color: rgb(254, 44, 36);">CLH(Craig,Landin,and Hagersten——三个大佬的人名)队列</span>,是<strong>单向链表</strong>实现的队列。申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱节点释放了锁就结束自旋。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/tnezsk4v.jpeg"></p> <p><br></p> <p>暂时介绍这么多,今天我们的重点是AQS变体的CLH队列。</p> <p><strong>该队列由一个个Node节点组成,每个Node节点维护一个prev和next引用,分别指向自己的前驱和后继节点,AQS维护两个指针,指向队列头部head和尾部tail。</strong></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/9ri2tpfo.jpeg"></p> <p>​</p> <p>当线程获取资源失败时,就会构造成一个Node节点加入CLH变体队列中,同时当前线程会被阻塞在队列中(通过<a href="http://LockSupport.park" target="_blank">LockSupport.park</a>实现)。当持有同步状态的线程释放同步状态时,会唤醒(通过<a href="http://LockSupport.unpark" target="_blank">LockSupport.unpark</a>实现)后继节点,然后此节点线程继续加入到对同步状态的争夺中。</p> <p><br></p> <h3>3.2.2 条件队列</h3> <p><br></p> <p>AQS内部提供了一个ConditonObject类,给维护独占同步的类以及实现Lock接口的类使用。</p> <p><br></p> <p>但是,有了CLH变种队列为什么还要条件队列呢?</p> <p><br></p> <p>因为CLH变种队列仅能解决<strong>线程阻塞和唤醒</strong>的问题,并不能提供条件和通知的功能。</p> <p><br></p> <p>因此,AQS引入了ConditionObject条件队列的概念,提供了一种更加高级的线程协作机制,能够更方便地实现特定条件的等待和唤醒。ConditonObject基于CLH变种队列实现,提供了信号通知、重入、公平性等特性,同时在使用时也更加方便和易于维护。</p> <p><br></p> <p>JUC包下的许多同步组件比如ReentrantLock、CyclicBarrier、Semaphore等,都有ConditionObject的身影。总之ConditionObject和CLH变种队列相辅相成,提供了一个完整、高效且灵活的线程协作机制,能够更好地支持更高级的线程同步操作。</p> <p><br></p> <h2>3.3 线程的阻塞和释放(LockSupport)</h2> <p><br></p> <p>在JSR166之前,阻塞和释放线程都是基于Java内置管程,唯一的选择的是<span style="color: rgb(254, 44, 36);">Thread.suspend</span>和<a href="http://Thread.resume" target="_blank" style="color: rgb(254, 44, 36);">Thread.resume</a>,但之前的文章也提到过,由于存在死锁的风险,这两个方法都被声明废弃了。即:<strong>如果两个线程同时持有一个线程对象,一个尝试去中断,一个尝试去释放,在并发情况下,无论调用时是否进行了同步,目标线程都存在死锁的风险——如果suspend()中的线程就是即将要执行resume()的那个线程,那肯定就要产生死锁了。</strong></p> <p><br></p> <p>JUC包有一个LockSuport类,它提供了另一种安全和可控的线程挂起和唤醒机制,以避免出现死锁和其它潜在问题:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>显式调用:使用LockSupport,线程的挂起和唤醒操作是显式的,需要开发者明确调用park和unpark方法。</li> <li data-list="bullet"><span class="ql-ui"></span>无需持有锁:LockSupport的park方法不会持有任何锁对象,因此不会引发死锁。线程在调用park方法挂起时,不会影响其他线程对锁的获取和释放操作。</li> <li data-list="bullet"><span class="ql-ui"></span>精确唤醒:LockSupport的unpark方法可以精确地唤醒指定的线程。与Thread的resume方法不同,unpark方法无需等待具体的操作,可以直接唤醒指定的线程。</li> <li data-list="bullet"><span class="ql-ui"></span>无状态变更:LockSupport的park和unpark方法不会导致线程状态的不一致性或其他潜在的问题。线程在被唤醒后,可以正常继续执行,遵循同步规则。</li> </ol> <p><br></p> <h1>四、acquire和release方法源码分析</h1> <p><br></p> <p>AQS里面最重要的就是两个操作和一个状态,即<strong>获取操作(acquire)、释放操作(release)和同步状态(state)。</strong>获取和释放操作又分为<strong>独占式</strong>和<strong>共享式</strong>,这两种模式大同小异,所以今天就只对独占模式下的<strong>获取(acquire)</strong>和<strong>释放(release)</strong>操作进行分析。</p> <p><br></p> <h2>4.1 相关属性</h2> <p><br></p> <p>再介绍之前我们先看一下相关的属性</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">final</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Node</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示共享模式static final Node SHARED = new Node();</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示独占模式static final Node EXCLUSIVE = null;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示线程已取消:由于在同步队列中等待的线程等待超时或中断//需要从同步队列中取消等待,节点进入该状态将不会变化(即要移除/跳过的节点)static final int CANCELLED = 1;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示后继节点处于park,需要唤醒:后继节点的线程处于park,而当前节点//的线程如果进行释放或者被取消,将会通知(signal)后继节点。static final int SIGNAL = -1;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示线程正在等待状态:即节点在等待队列中,节点线程在Condition上,//当其他线程对Condition调用signal方法后,该节点会从条件队列中转移到同步队列中static final int CONDITION = -2;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//表示下一次共享模式同步状态会无条件地传播下去static final int PROPAGATE = -3;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//节点的等待状态,即上面的CANCELLED/SIGNAL/CONDITION/PROPAGATE,初始值为0volatile int waitStatus;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//前驱节点volatile Node prev;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//后继节点volatile Node next;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//与当前节点关联的排队中的线程volatile Thread thread;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//同步模式改变时下一个等待节点</span> </div> <div class="ql-code-block"> Node nextWaiter; </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//判断是否是共享模式,若是则返回truefinal boolean isShared() {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">return</span> <span class="ql-token hljs-variable">nextWaiter</span> <span class="ql-token hljs-operator">=</span>= SHARED; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//返回节点的前驱节点,如果为null,则抛NullPointerException异常final Node predecessor() throws NullPointerException {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">Node</span> <span class="ql-token hljs-variable">p</span> <span class="ql-token hljs-operator">=</span> prev; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">if</span> (p == <span class="ql-token hljs-literal">null</span>) </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">NullPointerException</span>(); </div> <div class="ql-code-block"> elsereturn p; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//用于创建头节点或SHARED标记</span> </div> <div class="ql-code-block"> Node() { <span class="ql-token hljs-comment">// Used to establish initial head or SHARED marker</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> </div> <div class="ql-code-block"> Node(Thread thread, Node mode) { <span class="ql-token hljs-comment">// Used by addWaiterthis.nextWaiter = mode;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">this</span>.thread = thread; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> Node(Thread thread, <span class="ql-token hljs-type">int</span> waitStatus) { <span class="ql-token hljs-comment">// Used by Conditionthis.waitStatus = waitStatus;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-built_in">this</span>.thread = thread; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//同步队列的头节点,使用懒加载的方式初始化,仅能通过setHead修改。private transient volatile Node head;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//同步队列的尾节点,同样是懒加载。仅通过enq方法修改,用于添加新的等待节点private transient volatile Node tail;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//volatile修饰的状态变量stateprivate volatile int state;</span> </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//返回当前同步状态protected final int getState() {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> state; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//设置同步状态值protected final void setState(int newState) {</span> </div> <div class="ql-code-block"> state = newState; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"><span class="ql-token hljs-comment">//使用CAS修改同步状态值protected final boolean compareAndSetState(int expect, int update) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);</span> </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>如上所说,Node类其实就是构成<strong>CLH变体队列</strong>的一个个节点。</p> <p><br></p> <h2>4.2 acquire方法</h2> <p><br></p> <h3>4.2.1 acquire()</h3> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/1mzd2uis.jpeg"></p> <p><br></p> <p>分析:获取同步状态(锁)。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>tryAcquire():调用的是子类重写的方法,若返回true,则表示获取同步状态成功,后面就不再执行,反之则进入下一步。</li> <li data-list="bullet"><span class="ql-ui"></span>此时,获取同步状态失败,构造独占式同步节点,通过<span style="color: rgb(254, 44, 36);">addWaiter方法(见下)</span>将此节点添加到同步队列尾部,并调用<span style="color: rgb(254, 44, 36);">acquireQueued(见下)</span>方法尝试acquire。</li> <li data-list="bullet"><span class="ql-ui"></span>最后,如果acquireQueued返回ture,则调用selfInterrupt方法中断当前线程。</li> </ol> <p><br></p> <h3>4.2.2 addWaiter()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/a9kjsbj2.jpeg"></p> <p><br></p> <p>分析:根据当前线程和入参mode创建一个新Node,如果队列不为空则将Node设置为尾节点,反之则调用enq初始化队列并将node插入队列。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:以当前线程和mode为参数,创建一个节点node,将pred赋值为当前尾节点。</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:pred不为空。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>将新创建的节点的前驱节点设置为pre,即将创建的节点放到尾部。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>使用CAS将尾节点修改为新节点。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>若修改成功,则将pred的后继节点设置为新节点,并返回新节点node。</li> <li data-list="bullet"><span class="ql-ui"></span>第三个红框:如果pred为空,则代表此时同步队列为空,调用<span style="color: rgb(254, 44, 36);">enq方法(见下)</span>将新节点添加到同步队列,并返回node。</li> </ol> <p><br></p> <h3>4.2.3 enq()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/dh20vm6r.jpeg"></p> <p><br></p> <p>分析:与上述的addWaiter方法相似,只是多了一个队列为空时,初始化head和tail的操作(懒加载)。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>将t赋值为尾节点。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果尾节点为空,使用CAS将头节点赋值为一个新创建的无状态节点,并初始化尾节点。</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:如果尾节点不为空,使用CAS将当前node添加到尾节点。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>将node节点的前驱节点设置为t。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>使用CAS将尾节点设置为node。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>若设置成功,则修改node为t的后继节点,返回t。</li> </ol> <p><br></p> <h3>4.2.4 acquireQueued()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/5nvoxdmu.jpeg"></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/aht01z2f.jpeg"></p> <p><br></p> <p>分析:添加完节点后,立即尝试该节点是否能成功acquire。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:判断node节点的前驱节点p是否为头节点head,如果是则尝试acquire,若node成功acquire。则调用setHead方法将node设置为head、将node的Thread设置为null、将node的prev设置为null。将原头节点的next设置为null,也就是断开原head节点与node节点的关联,这就保证了头节点永远是一个不带Thread并且头节点的prev永远为null的空节点。</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:如果node节点的前驱节点不是head,或者node尝试acquire失败,则会调用<span style="color: rgb(254, 44, 36);">shouldParkAfterFailedAcquire方法(见下)</span>检验node是否需要park,如果返回true则调用<span style="color: rgb(254, 44, 36);">parkAndCheckInterrupt方法(见下)</span>将node的线程阻塞。</li> <li data-list="bullet"><span class="ql-ui"></span>第三个红框:若failed为true,则<strong>代表出现了异常</strong>,调用<span style="color: rgb(254, 44, 36);">cancelAcquire方法(见下)</span>取消正在进行acquire的尝试。</li> </ol> <p><br></p> <h3>4.2.5 shouldParkAfterFailedAcquire()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/a3dbh24v.jpeg"></p> <p><br></p> <p>分析:判断节点是否需要park。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:判断前驱节点的等待状态是否为SIGNAL,若是,则表示该node应该park,等待其它前驱节点来唤醒。(此时的pred是原节点的前驱节点)</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果前驱节点的等待状态大于0,也就是CANCELLED状态,也就是此节点已经无效,则需要从后往前遍历,找到一个非CANCELLED状态的节点,并将自己设置为它的后继节点。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果前驱节点的等待状态为其它状态,使用CAS尝试将pred节点的等待状态修改为SIGNAL,然后返回false。这就意味着再执行一次acquireQueued方法的第一个if,再次tryAcquire。</li> </ol> <p><br></p> <h3>4.2.6 parkAndCheckInterrupt()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/7hihpdgx.jpeg"></p> <p><br></p> <p>分析:直接调用LockSupport的park方法将当前线程阻塞,并在被唤醒之后,返回当前线程是否中断。</p> <p><br></p> <h3>4.2.7 cancelAcquire()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/znlrn79x.jpeg"></p> <p><br></p> <p>分析:取消正在等待获取独占同步状态的线程。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:首先判断传入的节点是否为空,为空就直接返回,不为空就将node的Thread设置为null。</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:如果node的前驱节点的等待状态为CANCELLED,则直接断开与该节点的联系。</li> <li data-list="bullet"><span class="ql-ui"></span>第三个红框:拿到pred的后继节点predNext(不一定是node了),并将node的等待状态设置为CANCELLED。如果node为尾节点,则CAS将尾节点改为pred节点,也就是把pred后面的节点全部移除(包括node节点和node节点前面等待状态为CANCELLED的节点)。</li> <li data-list="bullet"><span class="ql-ui"></span>第四个红框:后继节点的唤醒和更新</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>(判断当前节点是否为头节点)&amp;&amp;((获取node的前驱节点的等待状态赋值给ws并判断其等待状态是否为SIGNAL)||(判断ws是否是除CANCELLED状态之外的状态 &amp;&amp; 如果是则将其状态设置为SIGNAL ))&amp;&amp; 判断node的前驱节点的线程是否不为null</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果上述条件都满足,获取当前节点的后继节点next,如果next不为空且等待状态不为CANCELLED,则将前驱节点的后继节点设置为后继节点的后继节点,即跳过当前节点,因为只有pred的等待状态为SIGNAL才能走到这边,因此node的后继节点无需唤醒。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>反之,如果pred节点无法提供给node的后继节点信息,则直接唤醒node的后继节点(调用<span style="color: rgb(254, 44, 36);">unparkSuccessor方法(见下)</span>)。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>最后置空当前节点的引用,便于垃圾回收。</li> </ol> <p><br></p> <h3>4.2.8 unpakSuccessor()</h3> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/pu3jwdiy.jpeg"></p> <p><br></p> <p>分析:唤醒node节点的后继节点</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>第一个红框:将node节点的等待状态赋值给ws,如果ws小于0(即等待状态不是CANCELLED),则将ws的等待状态置为0(初始状态因为马上要将node的后继节点唤醒)。</li> <li data-list="bullet"><span class="ql-ui"></span>第二个红框:将node节点的后继节点赋值给s,如果s==null或者s的等待状态为CANCELLED,则直接将s置空,并从队列尾部向前遍历,找到等待状态不是CANCELLED的节点t(离node最近的节点),并将其赋值给s。这里的意思就是将node之后的空节点或等待状态为CANCELLED的节点也一并去掉,直接唤醒node之后等待状态不为CANCELLED的节点。</li> <li data-list="bullet"><span class="ql-ui"></span>第三个红框:如果s!=null,则执行<strong><a href="http://LockSupport.unpark" target="_blank">LockSupport.unpark</a>(s.thread)</strong>唤醒s节点。</li> </ol> <p><br></p> <h2>4.3 release()方法</h2> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/ud0nmaee.jpeg"></p> <p><br></p> <p>分析:释放同步状态。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>tryRelease():首先调用子类重写的tryRelease()方法,尝试释放锁。</li> <li data-list="bullet"><span class="ql-ui"></span>如果tryRelease()成功即释放锁成功,并且head节点不为空且等待状态不是初始状态,则调用<span style="color: rgb(254, 44, 36);">unparkSuccessor方法(见4.2.8)</span>唤醒head节点的后继节点。</li> </ol> <p><br></p> <h2>4.4 acquire方法总结</h2> <p><br></p> <p>release方法简单没什么好总结的,这里就总结一下acquire方法。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>首先,<span style="color: rgb(254, 44, 36);">acquire</span><span style="color: rgb(13, 0, 22);">方法</span>会调用<span style="color: rgb(254, 44, 36);">tryAcquire</span><span style="color: rgb(13, 0, 22);">方法</span>尝试直接获取锁。这个方法是由子类实现的,用于决定是否允许当前线程获取锁。如果<span style="color: rgb(254, 44, 36);">tryAcquire</span>方法成功获取了锁,就直接返回。</li> <li data-list="bullet"><span class="ql-ui"></span>如果<span style="color: rgb(254, 44, 36);">tryAcquire</span>方法无法直接获取锁,当前线程会通过调用<span style="color: rgb(254, 44, 36);">addWaiter</span>方法将该线程添加到等待队列中,如果队列不为空则将node放置队列尾部,如果为空则调用<span style="color: rgb(254, 44, 36);">enq</span>方法初始化队列,并放置队列尾部。</li> <li data-list="bullet"><span class="ql-ui"></span>接下来会调用<span style="color: rgb(254, 44, 36);">acquireQueued</span>方法,线程进入自旋状态,期间会不断尝试获取锁。首先会检查该节点的前驱节点是否为head节点,如果是则意味着当前节点是老二节点,可以再次调用<span style="color: rgb(254, 44, 36);">tryAcquire</span>方法尝试获取锁,如果获取锁成功,那么它将成为head节点,并将head节点的前驱节点置为null。如果不是头节点或者获取锁失败,则会调用<span style="color: rgb(254, 44, 36);">shouldParkAfterFailedAcquire</span>方法判断当前是否需要park。</li> <li data-list="bullet"><span class="ql-ui"></span>如果在<span style="color: rgb(254, 44, 36);">acquireQueued</span>中发生异常,则会执行<span style="color: rgb(254, 44, 36);">cancelAcquire</span>方法取消正在等待获取独占同步状态的线程。</li> <li data-list="bullet"><span class="ql-ui"></span>最后如果<span style="color: rgb(254, 44, 36);">acquireQueued</span>方法返回true,则调用<span style="color: rgb(254, 44, 36);">selfInterrupt</span>方法中断当前线程,这是因为返回ture就代表线程被中断。</li> </ol> <p><strong style="color: rgb(255, 153, 0);">End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。</strong></p> <p><br></p> </div> </body> </html>

Java并发编程第10讲——CAS相关知识点详解

<html> <head></head> <body> <div class="content ql-editor"> <p>全文12228字。</p> <p>各位球友,大家早上好😊。今天带来Java并发编程的第十讲——CAS,之前在介绍”锁“的时候也顺便提到过,不过介于它的重要性那还不够,所以今天详细地介绍一下和它相关的知识点。目录如下:</p> <p><br></p> <p>一、CAS相关概念介绍。</p> <p>二、举个经典的i++例子并给出几种解决方案,顺便引出CAS。</p> <p>三、12个Atomic原子操作类:包含基本、数组、引用和字段类型原子操作类的介绍、常用方法以及例子。</p> <p>四、CAS实现原理:以AtomicInteger的getAndIncrement方法源码分析开始,着重地介绍了Unsafe类,以及CAS的C++源码分析再到实现CAS操作的汇编指令——<span style="background-color: rgb(255, 255, 255); color: rgb(77, 77, 77);">cmpxchg指令介绍。</span></p> <p><span style="background-color: rgb(255, 255, 255); color: rgb(77, 77, 77);">五、CAS的三个问题:ABA、忙等待以及只能保证一个共享变量的原子操作等问题。</span></p> <p><span style="background-color: rgb(255, 255, 255); color: rgb(77, 77, 77);">六、LongAddr:LongAddr的相关概念、实现原理(add和sum方法源码解析)、最后介绍了LongAddr计算不准确的问题以及它的使用场景。</span></p> <p><br></p> <p><strong style="color: rgb(230, 115, 0);">推荐移步至原文博客:</strong><a href="https://blog.csdn.net/weixin_45433817/article/details/134016004?spm=1001.2014.3001.5502" target="_blank">Java并发编程第10讲——CAS相关知识点详解</a></p> <p><br></p> <p><strong style="color: rgb(204, 0, 0);">正文开始。。。</strong></p> <p><br></p> <p>前面介绍锁的时候顺便也提到了CAS,但作为JUC的“基石”和面试中的高频考点,还是不够。所以,本篇文章将从CAS的概念入手,逐步深入介绍<strong>12个Atomic原子操作类、CAS的实现原理(源码解析)、Unsafe类、CAS存在的问题以及LongAddr</strong>。</p> <p><br></p> <h1>一、什么是CAS</h1> <p><br></p> <p>CAS全称<span style="color: rgb(254, 44, 36);">Compare And Swap</span>,顾名思义就是先比较再交换。主要应用就是实现乐观锁和锁自旋。CAS操作包含三个操作数——<strong>内存位置(V)、预期值(A)和新值(B)。在并发修改的时候,会先比较A和V的值是否相等,如果相等,则会把值替换成B,否则就不做任何操作。</strong></p> <p><strong>当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程不会被挂起,而是被告知这次竞争失败,并可以再次尝试。</strong></p> <p>在JDK1.5中新增的<a href="http://java.util.concurrent" target="_blank" style="color: rgb(254, 44, 36);">java.util.concurrent</a><span style="color: rgb(254, 44, 36);">(JUC)</span>就是建立在CAS之上的。相对于synchronized这种阻塞型的同步,CAS是非阻塞算法的一种常见实现,所以JUC在性能上有了很大的提升。</p> <p><br></p> <h1>二、举个例子</h1> <p><br></p> <p>我们先看一个经典的例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Demo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">volatile</span> <span class="ql-token hljs-type">int</span> i=<span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">increase()</span>{ </div> <div class="ql-code-block"> i++; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> InterruptedException { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//启动10个线程,每个线程累加10000次for (int j = 0; j &lt; 10; j++) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Thread</span>(() -&gt; { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">k</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; k &lt; <span class="ql-token hljs-number">10000</span>; k++) { </div> <div class="ql-code-block"> increase(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }, String.valueOf(j)).start(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (Thread.activeCount() &gt; <span class="ql-token hljs-number">2</span>) { </div> <div class="ql-code-block"> Thread.yield(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> System.out.println(<span class="ql-token hljs-string">"最后i的值为:"</span> + i); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><span style="background-color: rgba(220, 220, 220, 0.5);"><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/j8bvmtzb.jpeg"></span></p> <p><br></p> <p>这个例子在<a href="https://blog.csdn.net/weixin_45433817/article/details/132395341?spm=1001.2014.3001.5501" target="_blank">Java并发编程第5讲——volatile关键字</a>中也用过,启动10个线程,每个线程累加10000次,那么i的预期的结果是100000,但运行完这段代码得到的结果都是小于100000的数字。</p> <p>这是因为volatile只能保证可见性,无法保证原子性,而自增操作并不是一个原子操作,它会出现写回主存覆盖的问题,送一我们每次都无法获得想要的结果,那么如何解决呢?说白了,这也就是线程安全问题,那么我们可以从保障线程安全入手,以下提供几个方案:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>synchronized:线程内使用同步代码块,由JVM自身的机制来保障线程的安全性。</li> <li data-list="bullet"><span class="ql-ui"></span>Lock锁:基于AQS+CAS实现。</li> <li data-list="bullet"><span class="ql-ui"></span>Atomic原子类:这就是我们今天的主角,下面再细说。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>LongAdder原子类</strong>:和Atomic原子类在同一包下,不过LongAdder更适合于高并发场景下,特别是写大于读的场景,相较于Atomic性能更好,代价是消耗更多的空间,以空间换时间。(下面也会细说)。</li> </ol> <p><br></p> <h1>三、12个Atomic原子操作类</h1> <p><br></p> <p>Java从JDK 1.5开始提供了<a href="http://java.util.concurrent.atomic" target="_blank">java.util.concurrent.atomic</a>包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。</p> <p>因为变量的类型由很多种,所以在Atomic包里一共提供了12个类,可以分为4种类型,分别是<strong>基本类型、数组、引用、字段</strong>。Atomic包里的类基本都是使用<span style="color: rgb(254, 44, 36);">Unsafe</span>实现的包装类。</p> <p><br></p> <h2>3.1 基本类型(3个)</h2> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicBoolean:布尔类型</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicInteger:整型类型</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicLong:长整型类型</li> </ol> <p>它们三个提供的方法基本一样,所以就以AtomicInteger为例,下面是它常用的方法:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicInteger(int initialValue):构造方法,设置初始值,若不设置则初始值为0。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndIncrement():以原子方式进行+1操作,并返回自增前的值。</li> <li data-list="bullet"><span class="ql-ui"></span>int IncrementAndGet():以原子方式进行+1操作,并返回自增后的值。</li> <li data-list="bullet"><span class="ql-ui"></span>int decrementAndGet():以原子方式进行-1操作,并返回自减后的值。</li> <li data-list="bullet"><span class="ql-ui"></span>int get():返回当前值。</li> <li data-list="bullet"><span class="ql-ui"></span>int addAndGet(int delta):以原子方式将输入的值于当前值相加,并返回结果。</li> <li data-list="bullet"><span class="ql-ui"></span>boolean compareAndSet(int expect,int update):如果输入的值(expect)等于AtomicInteger的当前值,则以原子的方式将该值设置为输入的值(update),成功返回ture,反之返回false。</li> </ol> <p>现在,我们可以用AtomicInteger来改造一下上面的代码,使其正常运行:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Demo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> AtomicInteger i=<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">AtomicInteger</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">increase()</span>{ </div> <div class="ql-code-block"> i.getAndIncrement(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> InterruptedException { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//启动10个线程,每个线程累加1000次for (int j = 0; j &lt; 10; j++) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Thread</span>(() -&gt; { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">k</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; k &lt; <span class="ql-token hljs-number">10000</span>; k++) { </div> <div class="ql-code-block"> increase(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }, String.valueOf(j)).start(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (Thread.activeCount() &gt; <span class="ql-token hljs-number">2</span>) { </div> <div class="ql-code-block"> Thread.yield(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> System.out.println(<span class="ql-token hljs-string">"最后i的值为:"</span> + i.get());<span class="ql-token hljs-comment">//100000</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><strong>我们把i改成使用AtomicInteger定义,i++改为“i.incrementAndGet()”使其变成原子操作,所以我们可以确保每次都可以获得正确的结果,并且性能上也有了不错的提升。</strong></p> <p><br></p> <h2>3.2 数组类型(3个)</h2> <p><br></p> <p>通过原子的方式更新数组里的某个元素。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicIntegerArray:整型数组</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicLongArray:长整型数组</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicReferenceArray:引用类型数组</li> </ol> <p>这三个提供的方法也基本一样,下面以AtomicIntegerArray为例,先看一下它的常用方法:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicIntegerArray(int length):构造方法,创建指定长度的AtomicIntegerArray对象,所有元素的初始值都是0。</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicIntegerArray(int[] array):构造方法,使用指定的整型数组初始化AtomicIntegerArray对象,数组的长度就是AtomicIntegerArray的长度。</li> <li data-list="bullet"><span class="ql-ui"></span>int get(int index):获取指定索引处的元素值。</li> <li data-list="bullet"><span class="ql-ui"></span>int length:返回数组的长度。</li> <li data-list="bullet"><span class="ql-ui"></span>void set(int index,int newValue):设置指定索引处的新值。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndIncrement(int index):获取指定索引处的旧值,并将其+1。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndDecrement(int index):获取指定索引处的旧值,并将其-1。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndAdd(int index,int delta):获取指定索引处的旧值,并将其加delta。</li> </ol> <p>举个例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ArrayDemo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//指定长度为3的AtomicIntegerArray对象,初始值都是0</span> </div> <div class="ql-code-block"> AtomicIntegerArray array=<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">AtomicIntegerArray</span>(<span class="ql-token hljs-number">3</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//获取索引为0的元素值</span> </div> <div class="ql-code-block"> System.out.println(array.get(<span class="ql-token hljs-number">0</span>));<span class="ql-token hljs-comment">//0//给索引为1的元素值设置为5</span> </div> <div class="ql-code-block"> array.set(<span class="ql-token hljs-number">1</span>,<span class="ql-token hljs-number">5</span>); </div> <div class="ql-code-block"> System.out.println(array.get(<span class="ql-token hljs-number">1</span>));<span class="ql-token hljs-comment">//5</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <h2>3.3 引用类型(3个)</h2> <p><br></p> <p>如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicReference:原子更新引用类型。</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicStampedReference:在AtomicReference的基础上增加了版本号的功能。</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicMarkableReference:在AtomicReference的基础上增加了布尔标记的功能。</li> </ol> <p>下面就以AtomicReference为例,常用方法:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicReference(V initialValue):构造方法,创建一个AtomicReference对象,并将初始值设置为initialValue。</li> <li data-list="bullet"><span class="ql-ui"></span>V get():获取当前的引用值。</li> <li data-list="bullet"><span class="ql-ui"></span>void set(V newValue):设置新的引用值。</li> <li data-list="bullet"><span class="ql-ui"></span>boolean compareAndSet(V expect,V update):如果引用值于期望值相等,则设置update为新值。</li> <li data-list="bullet"><span class="ql-ui"></span>V getAndSet(V newValue):设置新的引用,并返回旧的引用。</li> </ol> <p>举个例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ReferenceDemo</span> { </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">Student</span> <span class="ql-token hljs-variable">student</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Student</span>(<span class="ql-token hljs-string">"张三"</span>, <span class="ql-token hljs-number">20</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//创建一个指定引用为Student的AtomicReference对象</span> </div> <div class="ql-code-block"> AtomicReference&lt;Student&gt; reference=<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">AtomicReference</span>&lt;&gt;(student); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//获取当前引用</span> </div> <div class="ql-code-block"> System.out.println(reference.get());<span class="ql-token hljs-comment">//ReferenceDemo.Student(name=张三, age=20)//修改student引用</span> </div> <div class="ql-code-block"> student.setAge(<span class="ql-token hljs-number">18</span>); </div> <div class="ql-code-block"> student.setName(<span class="ql-token hljs-string">"小黑子"</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//设置新的引用</span> </div> <div class="ql-code-block"> reference.set(student); </div> <div class="ql-code-block"> System.out.println(reference.get());<span class="ql-token hljs-comment">//ReferenceDemo.Student(name=小黑子, age=18)</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> <span class="ql-token hljs-meta">@AllArgsConstructor@NoArgsConstructor@Datastatic</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">Student</span>{ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span> String name; </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">private</span> <span class="ql-token hljs-type">int</span> age; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <h2>3.4 字段类型(3个)</h2> <p><br></p> <p>如果需要原子地更新某个类的某个字段时,那么就可以用原子更新字段类。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicIntegerFieldUpdater:整型,可以在指定的对象上进行原子更新操作,支持原子地设置、获取、增加、减少等操作。</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicLongFieldUpdater:长整型,可以在指定的对象上进行原子更新操作,支持原子地设置、获取、增加、减少等操作。</li> <li data-list="bullet"><span class="ql-ui"></span>AtomicReferenceFieldUpdater:引用类型,可以在指定的对象上进行原子更新操作,支持原子地设置、获取、增加、减少等操作。</li> </ol> <p>下面以AtomicIntegerFieldUpdater为例,常用方法:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>AtomicIntegerFieldUpdater&lt;U&gt; newUpdater(Class&lt;U&gt; tclass,String fieldName):用于操作指定类种的指定字段。</li> <li data-list="bullet"><span class="ql-ui"></span>int get(T obj):获取指定对象的字段的当前值。</li> <li data-list="bullet"><span class="ql-ui"></span>void set(T obj,int newValue):设置指定对象的字段的值为指定的新值。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndIncrement(T obj):以原子的方式将指定对象的字段+1,并返回旧值。</li> <li data-list="bullet"><span class="ql-ui"></span>int getAndDecrement(T obj):以原子的方式将指定对象的字段-1,并返回旧值。</li> </ol> <p>举个例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-meta">@Datapublic</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">IntegerUpdaterDemo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//字段必须是 volatile 类型的private volatile int score;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//创建AtomicIntegerFieldUpdater 实例private static final AtomicIntegerFieldUpdater&lt;IntegerUpdaterDemo&gt; SCORE_UPDATER =</span> </div> <div class="ql-code-block"> AtomicIntegerFieldUpdater.newUpdater(IntegerUpdaterDemo.class, <span class="ql-token hljs-string">"score"</span>); </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">IntegerUpdaterDemo</span> <span class="ql-token hljs-variable">demo</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">IntegerUpdaterDemo</span>(); </div> <div class="ql-code-block"> demo.setScore(<span class="ql-token hljs-number">80</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//使用AtomicIntegerFieldUpdater进行原子性更新操作int newValue = 90;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">oldValue</span> <span class="ql-token hljs-operator">=</span> SCORE_UPDATER.getAndSet(demo, newValue); </div> <div class="ql-code-block"> System.out.println(<span class="ql-token hljs-string">"原始值: "</span> + oldValue + <span class="ql-token hljs-string">", 更新后的值: "</span> + demo.getScore()); <span class="ql-token hljs-comment">//原始值: 80, 更新后的值: 90 </span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <h1>四、CAS实现原理</h1> <p><br></p> <p>下面以AtomicInteger原子整型类为例,分析一下CAS底层实现机制。</p> <p><br></p> <h2>4.1 AtomicInteger的getAndIncrement方法</h2> <p><br></p> <p>上面解决i++问题的时候用到了AtomicInteger的getAndIncrement方法,通过方法调用,可以发现<span style="color: rgb(254, 44, 36);">getAndIncrement</span>方法调用了<span style="color: rgb(254, 44, 36);">getAndAddInt</span>方法,最后调用的是Unsafe类的<span style="color: rgb(254, 44, 36);">compareAndSwapInt</span>方法,也就是我们今天要介绍的CAS:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/0l54jba5.jpeg"></p> <p><br></p> <p>compareAndSwapInt方法参数解释:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>var1:Unsafe对象本身,需要通过这个类获取value的内存偏移地址。</li> <li data-list="bullet"><span class="ql-ui"></span>var2:value变量的内存偏移地址。</li> <li data-list="bullet"><span class="ql-ui"></span>var4:需要增加或减少的数。</li> <li data-list="bullet"><span class="ql-ui"></span>var5:内存位置的最新值。</li> <li data-list="bullet"><span class="ql-ui"></span>var5+var4:要更新的最新值。</li> </ol> <p><strong>就是拿到内存位置的最新值var5,使用CAS尝试将内存位置的值修改为目标值var5+var4,如果修改失败,则获取改内存位置的新值var5,然后继续尝试,直到成功。</strong></p> <p><br></p> <h2>4.2 Unsafe类</h2> <p><br></p> <p>读到这我们知道Java中Atomic类的实现都是基于Unsafe类实现的,那么Unsafe类是什么呢?</p> <p><br></p> <h3>4.2.1 什么是Unsafe类</h3> <p><br></p> <p><strong>Unsafe类是CAS的核心类。因为Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过JVM还是开了一个后门,JDK中有一个Unsafe类,他提供了硬件级别的原子操作,说白了Unsafe类就是跟底层硬件CPU指令通讯的复制工具。</strong></p> <p>Unsafe类包含了很多基础操作,比如数组操作、对象操作、内存操作、CAS操作、线程(park)操作,栅栏(Fence)操作,JUC包以及一些三方框架都是用Unsafe类来保证线程安全。</p> <p>Unsafe类在jdk源码的多个类中用到,这个类提供了一些绕开JVM的更底层的功能,基于它的实现可以提高效率。<strong>不过,正如它名字那样——不安全类,他所分配的内存需要手动free(不会被GC回收)。</strong></p> <p>Unsafe类提供了硬件级别的操作,主要有以下功能:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>通过Unsafe类可以分配内存,可以释放内存。</li> <li data-list="bullet"><span class="ql-ui"></span>可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的。</li> <li data-list="bullet"><span class="ql-ui"></span>将线程进行挂起与恢复。</li> <li data-list="bullet"><span class="ql-ui"></span>CAS操作。</li> </ol> <p><br></p> <h3>4.2.2 举个例子</h3> <p><br></p> <p>Unsafe被设计的初衷,并不是希望被一般开发者调用,它的构造方法是私有的,所以我们不能通过new或工厂方法实例化Unsafe对象,通常用反射来获取Unsafe示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">private</span> <span class="ql-token hljs-keyword">static</span> Unsafe <span class="ql-token hljs-title">getUnsafeInstance()</span> <span class="ql-token hljs-keyword">throws</span> NoSuchFieldException, IllegalAccessException { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//Unsafe类中提供了一个静态的getUnsafe方法,可以返回一个Unsafe实例Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");</span> </div> <div class="ql-code-block"> unsafeField.setAccessible(<span class="ql-token hljs-literal">true</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> (Unsafe) unsafeField.get(<span class="ql-token hljs-literal">null</span>); </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>来看一下分配内存的例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">UnsafeDemo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> NoSuchFieldException, IllegalAccessException { </div> <div class="ql-code-block"> <span class="ql-token hljs-type">Unsafe</span> <span class="ql-token hljs-variable">unsafe</span> <span class="ql-token hljs-operator">=</span> getUnsafeInstance(); </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//分配一个包含10个整数的连续内存空间long size = 10 * Integer.BYTES;</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//分配内存long memoryAddress = unsafe.allocateMemory(size);</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//在内存中存储值for (int i = 0; i &lt; 10; i++) {</span> </div> <div class="ql-code-block"> unsafe.putInt(memoryAddress + i * Integer.BYTES, i); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; i &lt; <span class="ql-token hljs-number">10</span>; i++) { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//从内存中读取值int value = unsafe.getInt(memoryAddress + i * Integer.BYTES);</span> </div> <div class="ql-code-block"> System.out.println(value); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//释放内存</span> </div> <div class="ql-code-block"> unsafe.freeMemory(memoryAddress); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//获取Unsafe实例private static Unsafe getUnsafeInstance() throws NoSuchFieldException, IllegalAccessException {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-type">Field</span> <span class="ql-token hljs-variable">unsafeField</span> <span class="ql-token hljs-operator">=</span> Unsafe.class.getDeclaredField(<span class="ql-token hljs-string">"theUnsafe"</span>); </div> <div class="ql-code-block"> unsafeField.setAccessible(<span class="ql-token hljs-literal">true</span>); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">return</span> (Unsafe) unsafeField.get(<span class="ql-token hljs-literal">null</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>同样,也可以用Unsafe类实现一个CAS操作,这里就不做演示了。</p> <p><br></p> <h3>4.2.3 Unsafe类的compareAndSwapInt方法</h3> <p><br></p> <p>介绍完Unsafe类,我们书接4.1,接着继续深入探讨该方法,该方法在Unsafe中对应的<span style="color: rgb(254, 44, 36);">C++</span>源码如下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/moaop9ck.jpeg"></p> <p><br></p> <p>可以看到调用了Atomic::cmpxchg方法,那么我们再来看看Atomic::cmpxchg方法:</p> <p><img src="disableDownload">​</p> <p>标红框的地方就是调用了汇编指令cmpxchg,作用是比较并交换而CAS操作的原理就是基于硬件提供的原子操作指令——cmpxchg指令实现的:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>cmpxchg指令是一条原子指令。在cpu执行cmpxchg指令时,处理器会自动锁定总线,防止其它cpu刚问共享变量,然后执行比较和交换操作,最后释放总线。</li> <li data-list="bullet"><span class="ql-ui"></span>cmpxchg指令在执行期间,cpu会自动禁止中断。这样可以确保CAS操作的原子性,避免中断或其它干扰对操作的影响。</li> <li data-list="bullet"><span class="ql-ui"></span>cmpxchg指令时硬件实现的,可以保证其原子性和正确性。cpu中的硬件电路确保了cmpxchg指令的正确执行,以及对共享变量的访问原子性。</li> </ol> <p><span style="color: rgb(149, 111, 231);">ps:同样是因为cmpxchg指令,这个指令基于cpu缓存一致性协议实现的。在多个cpu中,所有核心的缓存都是一致的。当一个cpu核心执行cmpxchg指令时,其它cpu核心的缓存会自动更新,以确保对共享变量的访问是一致的。</span></p> <p><br></p> <h1>五、CAS存在的问题</h1> <p><br></p> <p>任何技术都不是十全十美的,强如硬件级别实现的CAS操作也会有瑕疵。</p> <p><br></p> <h2>5.1 ABA问题</h2> <p><br></p> <p>CAS算法实现一个<strong>重要前提是需要取出内存中某时刻的数据,而在下个时刻进行比较和交换,那么这个时间差会导致数据的变化。</strong></p> <p>比如,当线程1要修改A时,会先读取A的值,如果此时有一个线程2,经过一系列操作,将A修改为B,再由B修改为A,然后线程1在比较A时,发现A的值没有改变,于是就修改了。但此时A的版本已经不是最先读取的版本了,这就时ABA问题。</p> <p>如何解决?解决这个问题的办法也很简单,就是添加版本号,修改数据时带上一个版本号,如果版本号我数据的版本号一致就修改(同时修改版本号),否则就失败,或者改用传统的互斥同步方式。</p> <p><br></p> <h2>5.2 忙等待问题</h2> <p><br></p> <p>上面的getAndAddInt方法就是一个很好的例子,getAndAddInt方法执行时,如果CAS失败,会一直尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。</p> <p><br></p> <h2>5.3 只能保证一个共享变量的原子操作</h2> <p><br></p> <p>当对一个共享变量操作时,我们可以用CAS的方式来保证原子操作,但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候我们可能就会用锁来保证原子性。</p> <p><br></p> <h1>六、LongAdder</h1> <p><br></p> <p>在5.2中提到的忙等待问题会给CPU带来很大的开销,从而会使性能下降。当然在大部分场景下使用Atomic原子操作类还是绰绰有余的,但在多线程竞争比较激烈,也就是并发冲突比较大的情况下,就得考虑其它方案了,比如LongAddr。</p> <p><br></p> <h2>6.1 什么是LongAddr</h2> <p><br></p> <p>LongAddr是Java 8中推出的一个新的类,<strong>主要是为了解决Atomic原子操作类在多线程竞争激烈的情况下性能并不高的问题</strong>,它主要是采用<span style="color: rgb(254, 44, 36);">分段+CAS</span>的方式来提升原子操作的性能。</p> <p>但是性能高就意味着要有牺牲,而LongAddr就是典型的以<strong>空间换时间的实现方式</strong>,所以它需要用到更大的空间,而且<strong>LongAddr还可能存在结果不准确的问题</strong>,Atomic原子操作类并不会。</p> <p>将AtomicInteger的例子改造一下:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">LongAddrDemo</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> LongAdder i=<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">LongAdder</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">increase()</span>{ </div> <div class="ql-code-block"> i.increment(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> InterruptedException { </div> <div class="ql-code-block"> <span class="ql-token hljs-comment">//启动10个线程,每个线程累加1000次for (int j = 0; j &lt; 10; j++) {</span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Thread</span>(() -&gt; { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">for</span> (<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">k</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">0</span>; k &lt; <span class="ql-token hljs-number">10000</span>; k++) { </div> <div class="ql-code-block"> increase(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> }, String.valueOf(j)).start(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (Thread.activeCount() &gt; <span class="ql-token hljs-number">2</span>) { </div> <div class="ql-code-block"> Thread.yield(); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> System.out.println(i.sum());<span class="ql-token hljs-comment">//100000</span> </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <h2>6.2 LongAddr实现原理(源码解析)</h2> <p><br></p> <p>Java 8中的LongAddr类继承自抽象类<span style="color: rgb(254, 44, 36);">Striped64</span>(也是atomic包下的),这个类也是Java 8新增的,主要的作用就是给并发场景下提供<strong>计数</strong>支持。</p> <p>Striped64的设计思路与ConcurrentHashMap类似,都是通过分散竞争的方式来提升并发性能,其中主要依赖了两个字段:<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/oa9nvu4i.jpeg"></p> <p><br></p> <p>解释:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>cells数组:当检测到有base变量被线程占有时,就会尝试暂存在cells数组中。</li> <li data-list="bullet"><span class="ql-ui"></span>base变量:当没有线程竞争时,首先会改变base的值。</li> </ol> <p>最终的结果:base变量+cells数组中value。</p> <p>通过查看LongAddr中的add方法的代码,我们就能更好地理解:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/1lw4jk68.jpeg"></p> <p><br></p> <p>解释:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>外层if:判断是否需要通过base进行累加</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果cells数组!=null,说明已经存在分段累加器(cells数组),则不需要base进行累加。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>如果cells数组==null,即不存在分段累加器,那么就尝试使用cas进行base累加,失败则进入if进行更细粒度的累加。</li> <li data-list="bullet"><span class="ql-ui"></span>内层if:进一步判断是否需要进行细粒度的累加操作</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>再一次判断cells数组是否为空,为空则表示没有分段的累加器。不为空进入下一步。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>cells数组长度m是否小于0。不小于0进入下一步。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>getProbe()&amp;m通过探针索引获取cells数组中的某个位置的cell对象a,看是否有初始化。不为空进入下一步。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>将Cell对象a的value与v+x进行CAS操作。</li> </ol> <p>满足以上四个判断之一,则调用longAccumulate方法进行细粒度的累加操作。</p> <p><br></p> <p>总结一下,首先尝试通过CAS更新base的值,如果再竞争不激烈的情况下,是可以CAS成功的,如果成功,那么就和Atomic原子操作类一样了。如果失败,那么就代表竞争激烈,那么就会尝试通过cells数组来分散计数。Striped64根据线程来计算哈希,然后将不同的线程分散到不同Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,<strong>最后总计数则是base加上cells数组中的计数内容。</strong></p> <p>这跟ConcurrentHashMap的分段锁很像吧。最后再来看看它是怎么统计的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/dft0128s.jpeg"></p> <p><br></p> <p>很简单,就是将base和cells数组中的值都加起来得到最终的结果。</p> <p><br></p> <h2>6.3 LongAddr计算不准确的问题</h2> <p><br></p> <p>还是那句话,没有十全十美的技术,那么LongAddr为什么会存在计算不准确的情况呢?</p> <p>通过查看LongAddr的代码很容易可以发现一个问题,那就是LongAddr的累加结果可能是不准确的,而且sum这个方法的注释上也明确点处了这个问题:</p> <p>Returns the current sum. The returned value is NOT an atomic snapshot; invocation in the absence of concurrent updates returns an accurate result, but concurrent updates that occur while the sum is being calculated might not be incorporated.</p> <p>翻译过来就是:返回值不是原子快照;在没有并发更新的情况下调用会返回准确的结果,但是在计算总和时发生并发更新可能不会被纳入计算。举个极端的例子:<strong>在并发高的情况下,如果刚好返回sum的时候有其它原子操作进行了累加操作,那么这时候返回的sum就会不准。</strong></p> <p>总体来说,LongAddr在高并发情况下提供了更好的性能,但牺牲了计算的准确性,所以它更适合应用于并发竞争激烈,但对数据准确度要求并不是百分之百准确的场景,比如微博点赞、文章阅读量的统计等场景。</p> <p><br></p> <p><strong style="background-color: rgb(255, 255, 255); color: rgb(255, 153, 0);">End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。</strong></p> <p><br></p> </div> </body> </html>

Java并发编程第7讲——CompletableFuture、Future和ForkJoinPool(万字详解)

<html> <head></head> <body> <div class="content ql-editor"> <p>全文23275字(代码示例比较多,可以放心阅读)</p> <p><br></p> <p>球友们早上好!😁今天重点介绍一下CompletableFuture,后两大节简单介绍一下和CompletableFuture关系密切的Future接口和ForkJoinPool线程池,目录如下:</p> <p><br></p> <p>1、什么是CompletableFuture:包含CompletableFuture的介绍、jdk中的继承关系以及实现的两个接口:Future和CompletionStage接口。</p> <p>2、CompletableFuture的底层实现:介绍了几个重要技术手段,比如Completion链式异步处理、事件驱动、ForkJoinPool线程池、通过CompletionException捕获异常、CAS操作等。</p> <p>3、CompletableFuture的使用(包含大量示例):包含六个函数式接口的介绍、以及四个静态方法、结果传递、合并结果、异常处理、根据计算速度选用、等待一组任务完成等相关功能对应的14个方法,最后介绍了其他api和Java 9新增的api。</p> <p>4、Future接口:包含Future接口的介绍、相关的api、实现类FutureTask示例、与CompletableFuture对比的不足之处以及CompletableFuture是如何解决的</p> <p>5、ForkJoinPool线程池:包含Fork/Join框架的介绍、ForkJoinPool的工作窃取算法和CompletableFuture选用ForkJoinPool作为默认线程池的原因。</p> <p><br></p> <p>这一篇在选技术点总结的时候纠结了很久😥,既想总结ThreadLocal又想总结CompletableFuture,ThreadLocal是因为前几天项目中用它解决了一个并发问题(想趁热打铁深入一波😀),而CompletableFuture是因为上一讲介绍线程池的时候提到过ForkJoinPool线程池,思来想去,还是把知识点总结的尽量紧凑一点,于是就选择了CompletableFuture。</p> <p><br></p> <p>推荐移步至原文博客:<a href="https://blog.csdn.net/weixin_45433817/article/details/132722432" target="_blank">Java并发编程第7讲——CompletableFuture、Future和ForkJoinPool(万字详解)</a>,效果更佳😀。</p> <p><br></p> <p><span style="color: rgb(236, 40, 52);">进入正文。</span></p> <p><br></p> <p><br></p> <p>在Java中进行异步计算是比较难以理解的。一般来说,我们希望将任何计算都视为一系列步骤,但是在异步的情况下,这些步骤通常以回调函数的形式存在,要么散布在代码中,要么互相嵌套的很深。而我们需要处理可能发生在某个步骤中的错误时,情况就变得更加复杂,而CompletableFuture就是来解决这些“困扰”的。</p> <p><br></p> <h1>一、什么是CompletableFuture</h1> <p><br></p> <p>CompletableFuture是Java 8中引入的一个类,用于异步编程和并发操作,它大约提供了<strong>50</strong>个不同的方法,用于<strong>组合、合并和执行异步</strong>计算步骤以及处理错误。</p> <p>下面是它的继承关系:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/k1v2xbrv.jpeg"></p> <p><br></p> <p>从图中可以看出,CompletableFuture类实现了<span style="color: rgb(254, 44, 36);">Future</span>和<span style="color: rgb(254, 44, 36);">CompletionStage</span>接口,这两个接口分别代表了异步任务的<strong>结果和完成状态</strong>,以及异步任务之间的依赖关系。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span><strong>Future:</strong>Java 5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,那么我们可以通过Future把这个任务放进异步线程中执行,并且可以通过Future获取计算结果。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>CompletionStage</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>他可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后出发一些函数或执行某些动作。</li> </ol> <p><br></p> <h1>二、CompletableFuture的底层实现</h1> <p><br></p> <p>底层实现主要涉及到了几个重要的技术手段,如<strong>Completion链式异步处理、事件驱动、ForkJoinPool线程池、通过CompletionException捕获异常、CAS</strong>操作等。</p> <ol> <li data-list="bullet"><span class="ql-ui"></span><strong>链式结构:</strong>CompletableFuture内部采用了一种链式结构来处理异步计算的结果,每个CompletableFuture都有一个与之关联的Completion链,它可以包含多个Completion阶段,每个阶段都代表一个异步操作,并且可以指定它所依赖的前一个计算结果。【在CompletableFuture类中,定义了一个<strong>内部类Completion</strong>,它表示Completion链的一个阶段,其中<strong>包含了前一个阶段的计算结果、下一个阶段的计算操作以及执行计算操作的线程池等信息。</strong>】</li> <li data-list="bullet"><span class="ql-ui"></span><strong>事件驱动:</strong>CompletableFuture还使用了一种事件驱动的机制来处理异步计算的完成事件。在一个ComletableFuture对象上注册的Completion阶段完成后,它会出发一个完成事件,然后CompletableFuture对象会执行与之关联的下一个Completion阶段。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>ForkJoinPool:</strong>CompletableFuture的异步计算是通过线程池来实现的。它在内部使用了一个ForkJoinPool线程池来执行异步任务。ForkJoinPool是一种特殊的线程池,使用了<strong>工作窃取算法</strong>来提高多线程的执行效率、可以根据需要动态地创建和回收线程、线程之间还可以共享任务,从而更好地利用计算资源。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>捕获异常:</strong>CompletableFuture会通过内部的CompletionException来捕获计算过程中出现的异常。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>CAS操作:</strong>CompletableFuture在状态转换和任务完成通知的过程中,使用CAS操作来保证线程安全。</li> </ol> <p><br></p> <h1>三、CompletbaleFuture的使用</h1> <p><br></p> <h2>3.0 六个函数式接口</h2> <p><br></p> <p>CompletableFuture方法的入参大部分是函数式接口,在学习之前先了解一下还是必要的。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/67ic3q78.jpeg"></p> <p>​</p> <h2>3.1 四个静态方法</h2> <p><br></p> <p>CompletableFuture提供了四个静态方法来创建一个异步操作:</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">runAsync(Runnable runnable)public</span> <span class="ql-token hljs-keyword">static</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">runAsync(Runnable runnable, Executor executor)public</span> <span class="ql-token hljs-keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">supplyAsync(Supplier&lt;U&gt; supplier)public</span> <span class="ql-token hljs-keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">supplyAsync(Supplier&lt;U&gt; supplier, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>runAsync()方法用Runnable函数时接口类型作为参数,无返回值,supplyAsync()方法以Supplier函数式接口作为参数,有返回值【用get()方法以阻塞的形式获取返回计算结果】。</li> <li data-list="bullet"><span class="ql-ui"></span>Executor参数可传可不传,若不传,就用默认的ForkJoinPool线程池,反之用传入的线程池。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">FourStaticDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> ExecutionException, InterruptedException { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//1.无返回值且默认线程池</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Void&gt; future = CompletableFuture.runAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"无返回值"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//2.有返回值且自定义线程池</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"有返回值"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"hello world"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }, Executors.newFixedThreadPool(<span class="ql-token hljs-number">1</span>)); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//获取返回值(阻塞)String s = future1.get();</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"s = "</span> + s); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>有没有不阻塞获取结果的方法?当然有,可以用:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">completedFuture(U value)</span> </div> </div> <p><br></p> <p><strong>如果我们已经知道计算结果</strong>,我们可以用静态的completedFuture()方法,该方法接收一个表示计算结果的参数,因此,<strong>它的get()方法不会阻塞</strong>,而是立即返回这个结果。</p> <p><br></p> <h2>3.2 结果传递</h2> <p><br></p> <h3>3.2.1 thenApply()</h3> <p>它接收一个Function示例,用它来处理结果。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenApply(Function&lt;? super T,? extends U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenApplyAsync(Function&lt;? super T,? extends U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenApplyAsync(Function&lt;? super T,? extends U&gt; fn, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenApply():将前面任务的执行结果,交给后面的Function。</li> <li data-list="bullet"><span class="ql-ui"></span>thenApplyAsync():异步执行。</li> </ol> <p><br></p> <p>示例:thenApply入参传入Function函数式接口,重写apply方法。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenApplyDemo</span> { </div> <div class="ql-code-block"> ​ </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> ExecutionException, InterruptedException { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//返回一个"hello"</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="ql-token hljs-string">"hello"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = future.thenApply(<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Function</span>&lt;String, String&gt;() { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-meta">@Overridepublic</span> String <span class="ql-token hljs-title">apply(String s)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> s+<span class="ql-token hljs-string">" world"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">String</span> <span class="ql-token hljs-variable">s</span> <span class="ql-token hljs-operator">=</span> future1.get(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(s);<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><span style="color: rgb(149, 111, 231);">ps:上面是完整的写法,后面就用Lambda表达式的简化写法了哈。</span></p> <p><br></p> <h3>3.2.2 thenAccept()</h3> <p>如果我们不需要在Future链中返回一个值,我们可以使用Consumer功能接口的实例。</p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAccept(Consumer&lt;? super T&gt; action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAcceptAsync(Consumer&lt;? super T&gt; action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAcceptAsync(Consumer&lt;? super T&gt; action,Executor executor)</span> </div> </div> <p><br></p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenAccept():将前面任务的执行结果,交给后面的Consumer。</li> <li data-list="bullet"><span class="ql-ui"></span>thenAcceptAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenAcceptDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="ql-token hljs-string">"hello"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Void&gt; future1 = </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;future.thenAccept(s -&gt; System.out.println(s + <span class="ql-token hljs-string">" world"</span>));<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h3>3.2.3 thenRun()</h3> <p><br></p> <p>如果我们既不需要计算结果的值,也不想在链的最后返回任何值,那么我们可以使用thenRun()方法。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenRun(Runnable action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenRunAsync(Runnable action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenRunAsync(Runnable action,Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenRun():将前面任务的执行结果,交给后面的Runnable。</li> <li data-list="bullet"><span class="ql-ui"></span>thenRunAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenRunDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="ql-token hljs-string">"hello"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Void&gt; future1 = </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;future.thenRun(() -&gt; System.out.println(<span class="ql-token hljs-string">"hello world"</span>));<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h3>3.2.4 thenApply()+thenAccpet()</h3> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ApplyAcceptDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">ExecutorService</span> <span class="ql-token hljs-variable">threadPool</span> <span class="ql-token hljs-operator">=</span> Executors.newFixedThreadPool(<span class="ql-token hljs-number">3</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">1</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }, threadPool).thenApply(f -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> f + <span class="ql-token hljs-number">2</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }).thenApply(f -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> f + <span class="ql-token hljs-number">2</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }).thenAccept(r -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(r);<span class="ql-token hljs-comment">//5</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <p><br></p> <h2>3.3 合并结果</h2> <p><br></p> <p>CompletableFuture API最好的部分是能够将CompletableFuture实例组合成计算步骤链条。</p> <p><br></p> <h3>3.3.1 thenCompose()</h3> <p><br></p> <p>此方法接收一个返回CompletableFuture实例的函数。该函数的参数是上一步计算的结果。这使得我们可以在下一个CompletableFuture的lambda表达式中使用这个值:</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenCompose(Function&lt;? super T, ? extends CompletionStage&lt;U&gt;&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenComposeAsync(Function&lt;? super T, ? extends CompletionStage&lt;U&gt;&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">thenComposeAsync(Function&lt;? super T, ? extends CompletionStage&lt;U&gt;&gt; fn,Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回。</li> <li data-list="bullet"><span class="ql-ui"></span>thenComposeAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenCompose</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> ExecutionException, InterruptedException { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; <span class="ql-token hljs-string">"hello"</span>) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .thenCompose(s -&gt; CompletableFuture.supplyAsync(() -&gt; s + <span class="ql-token hljs-string">" world"</span>)); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">String</span> <span class="ql-token hljs-variable">s</span> <span class="ql-token hljs-operator">=</span> future.get(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(s);<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <h3>3.3.2 thenCombine()</h3> <p><br></p> <p>如果我们想执行两个独立的Futures并对它们的结果进行处理,我们可以使用thenCombine方法,该方法接收一个Future和一个带有两个参数的函数来处理两个结果。</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U,V&gt; CompletableFuture&lt;V&gt; <span class="ql-token hljs-title">thenCombine(CompletionStage&lt;? extends U&gt; other,</span> </div> <div class="ql-code-block"><span class="ql-token hljs-params">BiFunction&lt;? super T,? super U,? extends V&gt; fn)public</span> &lt;U,V&gt; CompletableFuture&lt;V&gt; <span class="ql-token hljs-title">thenCombineAsync(CompletionStage&lt;? extends U&gt; other,BiFunction&lt;? super T,? super U,? extends V&gt; fn)public</span> &lt;U,V&gt; CompletableFuture&lt;V&gt; <span class="ql-token hljs-title">thenCombineAsync(CompletionStage&lt;? extends U&gt; other,BiFunction&lt;? super T,? super U,? extends V&gt; fn, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenCombine():合并两个任务。</li> <li data-list="bullet"><span class="ql-ui"></span>thenCombineAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenCombineDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> Exception { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; <span class="ql-token hljs-string">"hello"</span>) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .thenCombine(CompletableFuture.supplyAsync(() -&gt; <span class="ql-token hljs-string">" world"</span>), (s1, s2) -&gt; s1 + s2); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">String</span> <span class="ql-token hljs-variable">s</span> <span class="ql-token hljs-operator">=</span> future.get(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(s);<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><br></p> <p><br></p> <h3>3.3.3 thenAcceptBoth()</h3> <p><br></p> <p>一个更简单的情况是当我们相对<strong>两个Future的结果进行操作</strong>,但不需要将任何结果传递给Future链中时,可以使用thenAcceptBoth()方法。</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAcceptBoth(CompletionStage&lt;? extends U&gt; other,BiConsumer&lt;? super T, ? super U&gt; action)public</span> &lt;U&gt; CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAcceptBothAsync(CompletionStage&lt;? extends U&gt; other,BiConsumer&lt;? super T, ? super U&gt; action)public</span> &lt;U&gt; CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">thenAcceptBothAsync(CompletionStage&lt;? extends U&gt; other,BiConsumer&lt;? super T, ? super U&gt; action, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>thenAcceptBoth():两个任务执行完成后,将结果交给thenAcceptBoth处理。</li> <li data-list="bullet"><span class="ql-ui"></span>thenAcceptBothAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ThenAccpetBothDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(()-&gt;<span class="ql-token hljs-string">"hello"</span>) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .thenAcceptBoth(CompletableFuture.supplyAsync(()-&gt;<span class="ql-token hljs-string">" world"</span>),(s1,s2)-&gt; System.out.println(s1+s2)); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h2>3.4 异常处理</h2> <p><br></p> <p>在一系列异步计算步骤中处理错误,抛出/捕获使我们惯用的方式。</p> <p><br></p> <h3>3.4.1 whenComplete()</h3> <p><br></p> <p>CompletableFuture类允许我们用whenComplete()方法来处理异常,参数是函数式接口Biconsumer,可以接收两个参数:计算的结果(如果成功完成)和抛出的异常(没有正常完成)</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">whenComplete(BiConsumer&lt;? super T, ? super Throwable&gt; action)public</span> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">whenCompleteAsync(BiConsumer&lt;? super T, ? super Throwable&gt; action)public</span> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">whenCompleteAsync(BiConsumer&lt;? super T, ? super Throwable&gt; action, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>whenComplete():当任务完成时,可以使用结果和此阶段的异常执行操作。</li> <li data-list="bullet"><span class="ql-ui"></span>whenCompleteAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">WhenCompleteDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//除0异常</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; { <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">1</span> / <span class="ql-token hljs-number">0</span>; <span class="ql-token hljs-keyword">return</span> i;}) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .whenComplete((v, e) -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">if</span> (e != <span class="ql-token hljs-literal">null</span>) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//出现异常,处理。。</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"出现异常啦!异常:"</span>+e.getMessage()); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//没有异常</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(v); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>运行结果:</p> <p><br></p> <h3>3.4.2 exceptionally()</h3> <p><br></p> <p>exceptionally()方法是纯处理异常的操作,参数为Function函数式接口,接收的是一个Throwable类型的异常。</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">exceptionally(Function&lt;Throwable, ? extends T&gt; fn)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>exceptionally():此方法前的链中如果出现异常会走该方法,一般跟whenComplete配合使用,捕获范围包含此方法前的所有链中的异常。</li> </ol> <p><br></p> <p><span style="color: rgb(149, 111, 231);">ps:出现异常才会走,而whenComplete()出没出现都会走。</span></p> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ExceptionallyDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; {<span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">1</span> / <span class="ql-token hljs-number">1</span>; <span class="ql-token hljs-keyword">return</span> i;}) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .exceptionally(e -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"出现异常啦!"</span> + e.getCause()); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">null</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h3>3.4.3 handle()</h3> <p><br></p> <p>handle()和whenComplete()类似,也是接收两个参数:计算的结果(如果成功完成)和抛出的异常(没有正常完成),不同的是handle()用的是BiFunction函数式接口。</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">handle(BiFunction&lt;? super T, Throwable, ? extends U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">handleAsync(BiFunction&lt;? super T, Throwable, ? extends U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">handleAsync(BiFunction&lt;? super T, Throwable, ? extends U&gt; fn, Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>handle():相当于whenComplete+exceptionally。</li> <li data-list="bullet"><span class="ql-ui"></span>handleAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">HandleDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//除0异常</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; { <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-variable">i</span> <span class="ql-token hljs-operator">=</span> <span class="ql-token hljs-number">1</span> / <span class="ql-token hljs-number">0</span>; <span class="ql-token hljs-keyword">return</span> i;}) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .handle((v, e) -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">if</span> (e != <span class="ql-token hljs-literal">null</span>) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//出现异常,处理。。</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"出现异常啦!异常:"</span>+e.getMessage()); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//没有异常return v+1;</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p><span style="color: rgb(149, 111, 231);">ps:乍一看,whenComplete()和handle()没有什么区别,其实不然。whenComplete()是以BiConsumer函数式接口作为参数,而BiConsumer没有返回值,就不能对上一任务的结果进行“替换”。handle()以BiFunction作为参数,它有返回值,这就意味着可以对上一任务的结果进行“替换”。说正式点就是,whenComplete()不能消费异常,而handle()可以消费异常(可以把"异常的结果"替换成“正常的结果”)。</span></p> <p><br></p> <h3>3.4.4 whenComplete()+exceptionally()+handle()</h3> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">WhenExceptionHandleDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future = CompletableFuture </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">1</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .handle((f, e) -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"----handle()----"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">if</span>(e==<span class="ql-token hljs-literal">null</span>){ </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> f + <span class="ql-token hljs-number">2</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">null</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .whenComplete((v, e) -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">if</span> (e == <span class="ql-token hljs-literal">null</span>) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//没有异常</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"result:"</span> + v); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(<span class="ql-token hljs-string">"抛个异常---"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .exceptionally(e -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(e.getCause()); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-literal">null</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>运行结果:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/d7xvyv1u.jpeg"></p> <p>​</p> <h2>3.5 根据计算速度选用</h2> <p><br></p> <p>这也比较好理解,就是看两个任务的处理速度,谁快就触发下一步的动作。</p> <p><br></p> <h3>3.5.1 applyToEither()</h3> <p><br></p> <p>两个任务谁快用谁的计算结果,有返回值。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">applyToEither(CompletionStage&lt;? extends T&gt; other, Function&lt;? super T, U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">applyToEitherAsync(CompletionStage&lt;? extends T&gt; other, Function&lt;? super T, U&gt; fn)public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">applyToEitherAsync(CompletionStage&lt;? extends T&gt; other,Function&lt;? super T, U&gt; fn,</span> </div> <div class="ql-code-block"><span class="ql-token hljs-params"> &nbsp; &nbsp; &nbsp; &nbsp;Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>applyToEither():两个任务比较,谁先获得计算结果,就用谁。</li> <li data-list="bullet"><span class="ql-ui"></span>applyToEitherAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">ApplyToEitherDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> Exception { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"A"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//睡0.1秒</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">100</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"B"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; result = future1.applyToEither(future2, v -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> v + <span class="ql-token hljs-string">"比较快"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">String</span> <span class="ql-token hljs-variable">res</span> <span class="ql-token hljs-operator">=</span> result.get(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(res);<span class="ql-token hljs-comment">//A比较快</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h3>3.5.2 accpetEither</h3> <p><br></p> <p>和applyToEither差不多,区别在于没有返回值。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">acceptEither(CompletionStage&lt;? extends T&gt; other, Consumer&lt;? super T&gt; action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">acceptEitherAsync(CompletionStage&lt;? extends T&gt; other, Consumer&lt;? super T&gt; action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">acceptEitherAsync(CompletionStage&lt;? extends T&gt; other, Consumer&lt;? super T&gt; action,Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>acceptEither():两个任务比较,谁先获得计算结果,就用谁。</li> <li data-list="bullet"><span class="ql-ui"></span>acceptEitherAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">AcceptToEitherDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> Exception { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"A"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//睡0.1秒</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">100</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"B"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Void&gt; result = future1.acceptEither(future2, v -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(v+<span class="ql-token hljs-string">"比较快"</span>);<span class="ql-token hljs-comment">//A比较快</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h3>3.5.3 runAfterEither()</h3> <p><br></p> <p>两个任务任一先执行完就进行下一步操作,不用计算结果,也无返回值。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">runAfterEither(CompletionStage&lt;?&gt; other,Runnable action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">runAfterEitherAsync(CompletionStage&lt;?&gt; other,Runnable action)public</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">runAfterEitherAsync(CompletionStage&lt;?&gt; other,Runnable action,Executor executor)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>runAfterEither():两个任务有任何一个执行完成,就进入下一步操作,无返回值。</li> <li data-list="bullet"><span class="ql-ui"></span>runAfterEitherAsync():异步执行。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">RunAfterEitherDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"A"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//睡0.1秒</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">100</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"B"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Void&gt; result = future1.runAfterEither(future2, () -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"有任务完成啦!!!"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//有任务完成啦!!!</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h2>3.6 等待一组任务完成</h2> <p><br></p> <p>当需要同时执行多个异步操作,并等待所有或者任何一个操作完成后,再进行下一步操作时,就可以考虑使用以下两个方法,一般配合join()使用。</p> <p><br></p> <h3>3.6.1 allOf()</h3> <p><br></p> <p>合并多个任务为一个,等待全部完成,无返回值。</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> CompletableFuture&lt;Void&gt; <span class="ql-token hljs-title">allOf(CompletableFuture&lt;?&gt;... cfs)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>allOf():多个任务全部执行完返回。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">AllOfDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">1000</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future1 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">1</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">2000</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future2 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">2</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Integer&gt; future3 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">3000</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future3 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-number">3</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//阻塞三个任务</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture.allOf(future1, future2, future3).join(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future1、future2、future3全部执行完毕!"</span>); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>输出结果:</p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/9afp7awo.jpeg"></p> <p>​</p> <h3>3.6.2 anyOf()</h3> <p><br></p> <p>合并多个任务为一个,任意一个执行完成就进行下一步操作,有返回值。</p> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> CompletableFuture&lt;Object&gt; <span class="ql-token hljs-title">anyOf(CompletableFuture&lt;?&gt;... cfs)</span> </div> </div> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>anyOf():多个任务任一执行完就返回。</li> </ol> <p><br></p> <p>示例:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">AllOfDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> ExecutionException, InterruptedException { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">100</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future1 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"A"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">200</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future2 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"B"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;String&gt; future3 = CompletableFuture.supplyAsync(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">try</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Thread.sleep(<span class="ql-token hljs-number">300</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } <span class="ql-token hljs-keyword">catch</span> (InterruptedException e) { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">throw</span> <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">RuntimeException</span>(e); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(<span class="ql-token hljs-string">"future3 执行"</span>); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"C"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//阻塞三个任务</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;CompletableFuture&lt;Object&gt; obj = CompletableFuture.anyOf(future1, future2, future3); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-type">Object</span> <span class="ql-token hljs-variable">o</span> <span class="ql-token hljs-operator">=</span> obj.get(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(o+<span class="ql-token hljs-string">"执行完了!"</span>); </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <p>输出结果:</p> <p>​<img src="https://pic.code-nav.cn/planet_post_image/1653736647323332609/wbff0m7v.jpeg"></p> <p>​</p> <h2>3.7 其它api和Java9新增的api</h2> <h3>3.7.1 Java 8</h3> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> T <span class="ql-token hljs-title">join()public</span> T <span class="ql-token hljs-title">getNow(T valueIfAbsent)public</span> <span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">isCompletedExceptionally()public</span> <span class="ql-token hljs-type">int</span> <span class="ql-token hljs-title">getNumberOfDependents()</span> </div> </div> <p><br></p> <p>解释:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>join():阻塞获取结果,不需要抛出异常。</li> <li data-list="bullet"><span class="ql-ui"></span>getNow():计算完成就返回正常值,否则返回备胎值(传入的参数),立即获取不阻塞。</li> <li data-list="bullet"><span class="ql-ui"></span>isCompletedExceptionally():判断任务是否异常结束。</li> <li data-list="bullet"><span class="ql-ui"></span>getNumberOfDependents():返回依赖当前任务的任务数量。</li> </ol> <p><br></p> <h3>3.7.2 Java 9</h3> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"> CompletableFuture&lt;U&gt; <span class="ql-token hljs-title">newIncompleteFuture()</span> </div> <div class="ql-code-block"> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">copy()</span> </div> <div class="ql-code-block"> CompletionStage&lt;T&gt; <span class="ql-token hljs-title">minimalCompletionStage()</span> </div> <div class="ql-code-block"> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">orTimeout(long timeout, TimeUnit unit)</span> </div> <div class="ql-code-block"> CompletableFuture&lt;T&gt; <span class="ql-token hljs-title">completeOnTimeout(T value, long timeout, TimeUnit unit)</span> </div> </div> <p><br></p> <p>解释:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>newIncompleteFuture():也被称为“虚拟构造函数”,用于获取相同类型的新的Completable实例。</li> <li data-list="bullet"><span class="ql-ui"></span>copy():返回一个新的CompletableFuture。</li> <li data-list="bullet"><span class="ql-ui"></span>minimalCompletionStage():返回一个新的CompletionStage。</li> <li data-list="bullet"><span class="ql-ui"></span>orTimeout():若在指定的超时时间之前未完成,则会以异常(TimeoutException)结束。</li> <li data-list="bullet"><span class="ql-ui"></span>completeOnTimeout():若在指定的超时时间之前未完成,则以给定的value为任务结果。</li> </ol> <p><br></p> <h1>四、Future接口</h1> <p><br></p> <p>简单介绍一下Future接口,Future是Java 5新加的一个接口,他在处理异步调用和并发处理时非常有用。</p> <p><br></p> <h2>4.1 什么是Future</h2> <p><br></p> <p>简单来说,Future类代表了一个异步计算的未来结果。这个结果将在处理完成后最终出现在Future中。</p> <p><br></p> <p><strong>如果主线程需要执行一个很耗时的计算任务,那么Future将是一个不错的选择,我们可以通过Future把这个任务放入异步线程中去执行,主线程就可以去处理其它任务或提前结束。</strong></p> <p><br></p> <h2>4.2 Future的相关Api</h2> <p><br></p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">cancel(boolean mayInterruptIfRunning)</span>; </div> <div class="ql-code-block"><span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">isCancelled()</span>; </div> <div class="ql-code-block"><span class="ql-token hljs-type">boolean</span> <span class="ql-token hljs-title">isDone()</span>; </div> <div class="ql-code-block"> V <span class="ql-token hljs-title">get()</span> </div> <div class="ql-code-block"> V <span class="ql-token hljs-title">get(long timeout, TimeUnit unit)</span> </div> </div> <p><br></p> <p>解释:</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>cancel():取消任务,若一个任务未完成,则以CancellationException异常结束。其相关未完成的子任务也会以此异常结束。</li> <li data-list="bullet"><span class="ql-ui"></span>isCancelled():判断任务是否已取消,若任务在正常执行前取消,则返回ture,反之返回false。</li> <li data-list="bullet"><span class="ql-ui"></span>isDone():判断任务是否完成。三种完成情况:正常执行完成、异常完成和已取消。</li> <li data-list="bullet"><span class="ql-ui"></span>get():以阻塞的方式获取计算结果,需要处理异常,捕获或者直接抛。</li> <li data-list="bullet"><span class="ql-ui"></span>get(long timeout,TimeUnit unit):若超过设置的时间还未获取到结果,就直接抛异常。</li> </ol> <p><br></p> <h2>4.3 FutureTask</h2> <p><br></p> <p>FutureTask也是Future的一个实现类,下面我们来看一个例子:</p> <p><br></p> <div class="ql-code-block-container"> <div class="ql-code-block"><span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">class</span> <span class="ql-token hljs-title">FutureTaskDemo</span> { </div> <div class="ql-code-block"> &nbsp; &nbsp;<span class="ql-token hljs-keyword">public</span> <span class="ql-token hljs-keyword">static</span> <span class="ql-token hljs-keyword">void</span> <span class="ql-token hljs-title">main(String[] args)</span> <span class="ql-token hljs-keyword">throws</span> Exception { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;FutureTask&lt;String&gt; task = <span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">FutureTask</span>&lt;&gt;(() -&gt; { </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">return</span> <span class="ql-token hljs-string">"hello world"</span>; </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; }); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-keyword">new</span> <span class="ql-token hljs-title">Thread</span>(task).start(); </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="ql-token hljs-comment">//阻塞获取结果</span> </div> <div class="ql-code-block"> &nbsp; &nbsp; &nbsp; &nbsp;System.out.println(task.get());<span class="ql-token hljs-comment">//hello world</span> </div> <div class="ql-code-block"> &nbsp; } </div> <div class="ql-code-block"> } </div> </div> <p><br></p> <h2>4.4 与CompletableFuture对比</h2> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span><strong>阻塞等待:</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>Future接口的get()方法在获取计算结果时会阻塞等待,可能导致线程资源浪费和性能问题。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>CompletableFuture提供了一系列非阻塞的方法,如thenApply()、thenCompose()等。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>无法手动完成或取消:</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>Future接口的实现类无法手动标记任务的完成或取消状态,只能依赖于任务本身的执行状态。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>CompletableFuture提供了如complete()、completeExceptionally()、cancel()等,以便手动设置异步任务的结果、异常或取消状态。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>缺乏异常梳理机制:</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>Future接口的get()方法在异步任务会抛出异常,但在任务完成前无法捕获和处理异常。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>CompletableFuture提供了exceptionally()和handle()方法,可以方便地对异步任务的异常情况进行处理和转换。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>缺少组合能力:</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>Future接口无法直接组合多个异步任务,不能方便的实现串行、并行等操作。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>CompletableFuture提供了一系列方法,如thenApply()、thenCompose()和thenCombine()等,可以方便地实现异步任务的组合。</li> <li data-list="bullet"><span class="ql-ui"></span><strong>缺少回调机制:</strong></li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>Future接口无法在异步任务完成时自动触发回调函数执行。</li> <li data-list="bullet" class="ql-indent-1"><span class="ql-ui"></span>CompletableFuture提供了如thenApply()、thenAccpept()、thenRun()等,可以在任务完成时自动执行回调函数。</li> </ol> <p><br></p> <p>总之一句话,Future能干的CompletableFuture都能干,Future不能干的CompletableFuture也能干!</p> <p><br></p> <h1>五、ForkJoinPool</h1> <p><br></p> <h2>5.1 Fork/Join框架</h2> <p><br></p> <p>Java 7引入了Fork/Join框架,它提供了一些工具,通过尽可能地提高cpu的利用率来加速并行处理,它通过分而治之的方法来实现这一点的。</p> <p><br></p> <p>实际上,这意味着该框架首先进行“fork”操作,递归地将任务分解为较小的独立子任务,知道它们足够简单可以异步运行。</p> <p><br></p> <p>之后,开始“join”部分。所有的子任务的结果被递归地合并为一个单一的结果。对于返回void的任务,程序会简单地等待每个子任务运行完毕。</p> <p><br></p> <p><strong>为了提供有效的并行执行,Fork/Join框架使用了一个线程池——ForkJoinPool。</strong></p> <p><br></p> <h2>5.2 ForkJoinPool</h2> <p><br></p> <p><strong>ForkJoinPool是该框架的核心。</strong>它是ExecutorService的一个实现,负责管<strong>理工作线程,并提供工具以获取有关线程池的状态和性能信息。</strong></p> <p><br></p> <p>工作线程一次只能执行一个任务,但ForkJoinPool并不为每个子任务创建一个单独的线程。相反,池中的每个线程都有自己的<strong>双端队列(deque)</strong>,用于存储任务。</p> <p><br></p> <h3>5.2.1 工作窃取算法</h3> <p><br></p> <p>简单来说,空闲的线程会尝试从繁忙线程的deque中“窃取”任务。</p> <p><br></p> <p>默认情况下,工作线程从自己的deque头部获取任务。当deque为空时,线程会从另一个繁忙线程的deque尾部获取任务,或者从全局入口队列获取任务,因为那里可能存放着最大的工作块。</p> <p><br></p> <p>这种方式最大程度地减少了线程竞争任务的可能性。它还减少了线程寻找任务的次数,因为首先处理最大的可用工作块。</p> <p><br></p> <h3>5.2.2 CompletableFuture使用ForkJoinPool的原因</h3> <p><br></p> <p>主要原因时因为它的执行模型和任务分割方式与ForlJoinPool更加匹配。</p> <p><br></p> <ol> <li data-list="bullet"><span class="ql-ui"></span>在CompletableFuture中,一个任务可以分割成多个子任务,并且这些子任务之间可以存在依赖关系。而ForkJoinPool本身就是一种支持任务分割和合并的线程池实现,能够自动地处理任务的拆分和合并。而且,ForkJoinPool还有一种工作窃取算法,能够自动地调整线程的负载,提高线程的利用率和并行度。</li> <li data-list="bullet"><span class="ql-ui"></span>ForkJoinPool还有一个特点,就是它的线程池大小是动态调整的。当任务比较小时,线程池的大小会自动缩小,从而减少线程的数量和占用的系统资源。当任务比较多时,线程池的大小会自动增加,从而保证任务能够及时地得到执行。</li> <li data-list="bullet"><span class="ql-ui"></span>如果使用ExcutorService来执行这些任务,需要手动地创建线程池、任务队列和任务执行策略,并且需要手动地处理任务的拆分和合并,实现起来比较复杂。</li> </ol> <p><br></p> <p><span style="color: rgb(255, 153, 0);">End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。</span></p> <p><br></p> </div> </body> </html>

下载 APP