JUC

718 阅读36分钟

前文接多线程,因为放到一起发布超过字数了,特分为两篇文章。

7、JUC

7.1、JUC是什么

JUC 是 Java 并发工具包(Java Util Concurrent)的缩写。它是 Java 标准库中一个重要的包,提供了丰富的工具和类,用于开发多线程和并发程序,旨在帮助开发人员更方便、更安全地处理并发编程中的各种问题。

7.2、JUC 的主要目的

  • 提供比 Java 内置的synchronized关键字和Object类的wait()notify()等方法更高级、更灵活的并发控制机制,以应对复杂的并发场景。
  • 提高并发程序的性能和可扩展性,减少线程间的同步开销,避免死锁和资源竞争等常见问题。

7.3、JUC的主要组件和类

7.3.1、原子类(Atomic Classes)

  • 位于java.util.concurrent.atomic包中,提供了对基本数据类型(如AtomicIntegerAtomicLongAtomicBoolean等)和对象引用(如AtomicReference)的原子操作支持。
  • 原子操作是指不会被线程调度机制打断的操作,保证操作的完整性和一致性。例如,AtomicInteger类可以实现线程安全的自增、自减、比较并交换(CAS)等操作,避免了使用synchronized关键字时的性能开销和可能的死锁问题。

前文已讲。

7.3.2、锁(Locks)

  • java.util.concurrent.locks包中包含了比synchronized更灵活的锁机制。
  • ReentrantLock:可重入锁,提供了更灵活的锁定操作,支持尝试获取锁、可中断锁等特性。
7.3.2.1、ReentrantLock

前文已讲。

7.3.2.2、ReadWriteLock

读写锁,允许多个线程同时读,但只允许一个线程写,提高了并发性能。

例如:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
​
public class ReadWriteLockExample {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();
​
    public void read() {
        readLock.lock();
        try {
            // 读操作
        } finally {
            readLock.unlock();
        }
    }
​
    public void write() {
        writeLock.lock();
        try {
            // 写操作
        } finally {
            writeLock.unlock();
        }
    }
}

7.3.3、线程池

  • 线程池可以复用线程,避免频繁创建和销毁线程的开销。
  • ExecutorService接口是线程池的核心,Executors类提供了创建不同类型线程池的工厂方法。
7.3.3.1、ExecutorService类和Executors工具类

前文已讲。

7.3.3.2、ForkJoinPool
7.3.3.2.1、基本概念

ForkJoinPool 是 Java java.util.concurrent 包中的一个类,它是一种特殊的线程池,实现了 ExecutorService 接口,专门用于执行 ForkJoinTask 类型的任务。

7.3.3.2.2、特点

工作窃取算法:

ForkJoinPool 采用工作窃取(Work-Stealing)算法,每个工作线程都有自己的双端队列,当一个线程完成自己队列上的任务后,它可以从其他线程的队列末尾 “窃取” 任务,这样可以充分利用多核处理器的优势,提高并行计算的性能。

7.3.3.2.3、核心元素

ForkJoinTask:是一个抽象类,是ForkJoinPool中执行任务的基类,它有两个重要的子类:

  • RecursiveTask:有返回结果的任务,需要重写 compute() 方法,在该方法中可以将任务分解为子任务,并使用 fork() 方法提交子任务,使用 join() 方法获取子任务的结果。
  • RecursiveAction:无返回结果的任务,也需要重写 compute() 方法进行任务分解和提交,但不返回结果。
7.3.3.2.4、使用 RecursiveTask 进行求和计算
package com.wxx.pool;
​
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
​
public class ForkJoinPoolDemo {
    static class SumTask extends RecursiveTask<Integer> {
        private final int[] array;
        private final int start;
        private final int end;
        private static final int THRESHOLD = 10;
​
        SumTask(int[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }
​
        @Override
        protected Integer compute() {
            if (end - start <= THRESHOLD) {
                int sum = 0;
                for (int i = start; i < end; i++) {
                    sum += array[i];
                }
                return sum;
            } else {
                int mid = (start + end) / 2;
                SumTask leftTask = new SumTask(array, start, mid);
                SumTask rightTask = new SumTask(array, mid, end);
                // 分解为子任务
                leftTask.fork();
                rightTask.fork();
                // 获取子任务结果
                return leftTask.join() + rightTask.join();
            }
        }
    }
​
    public static void main(String[] args) {
        int[] array = new int[100];
        for (int i = 0; i < 100; i++) {
            array[i] = i + 1;
        }
        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        SumTask task = new SumTask(array, 0, array.length);
        int result = forkJoinPool.invoke(task);
        System.out.println("Sum: " + result);
    }
}

代码解释:

  • SumTask 类继承 RecursiveTask<Integer>,用于计算数组元素的和。
  • compute() 方法中,如果任务大小小于等于 THRESHOLD,直接计算元素和;否则,将任务拆分为左右两个子任务(leftTaskrightTask)。
  • leftTask.fork()rightTask.fork() 将子任务提交到 ForkJoinPool 中执行。
  • leftTask.join()rightTask.join() 等待子任务完成并获取结果。
7.3.3.2.5、使用 RecursiveAction 进行打印操作
package com.wxx.pool;
​
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
​
/**
 * @Author: wangxiaoxuan
 * @Date: 2025/1/20
 * @Describe:
 */
public class ForkJoinPoolActionDemo {
    static class PrintTask extends RecursiveAction {
        private final int[] array;
        private final int start;
        private final int end;
        private static final int THRESHOLD = 10;
​
        PrintTask(int[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }
​
        @Override
        protected void compute() {
            if (end - start <= THRESHOLD) {
                for (int i = start; i < end; i++) {
                    System.out.println(array[i]);
                }
            } else {
                int mid = (start + end) / 2;
                PrintTask leftTask = new PrintTask(array, start, mid);
                PrintTask rightTask = new PrintTask(array, mid, end);
                leftTask.fork();
                rightTask.fork();
                leftTask.join();
                rightTask.join();
            }
        }
    }
​
    public static void main(String[] args) {
        int[] array = new int[100];
        for (int i = 0; i < 100; i++) {
            array[i] = i + 1;
        }
        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        PrintTask task = new PrintTask(array, 0, array.length);
        forkJoinPool.invoke(task);
    }
}

代码解释

  • PrintTask 类继承 RecursiveAction,用于打印数组元素。
  • compute() 方法中,如果任务大小小于等于 THRESHOLD,直接打印元素;否则,拆分为左右子任务。
  • 子任务通过 fork() 方法提交,通过 join() 方法等待完成。
7.3.3.2.6、与其他线程池的区别
  • 工作窃取机制:

    • 与普通的线程池(如 ThreadPoolExecutor)不同,ForkJoinPool 利用工作窃取算法,更适合处理可分解为子任务的计算密集型任务,提高并行度和性能。
    • 普通线程池通常使用阻塞队列存储任务,而 ForkJoinPool 使用工作窃取算法管理任务。
  • 计算密集型任务:

    • 适合大规模的数据处理、排序、矩阵运算、图像处理等可以分解为子任务的计算密集型任务。
    • 例如,对大规模数组进行并行求和、归并排序等操作。

7.3.4、并发容器

JUC提供了多种线程安全的容器类,用于在并发环境下安全地存储和访问数据。

7.3.4.1、ConcurrentHashMap
7.3.4.1.1、基本概念

ConcurrentHashMap 是 Java java.util.concurrent 包中的一个类,它是一个线程安全的哈希表,实现了 ConcurrentMap 接口,适用于高并发环境下的键值对存储和操作。

7.3.4.1.2、特点
  • 线程安全

    与 HashMap 不同,ConcurrentHashMap 可以在多线程环境下安全地进行读写操作,内部采用了分段锁(JDK 8 之前)或 CAS(Compare And Swap)操作和 synchronized 关键字(JDK 8 及以后)来实现线程安全,避免了对整个哈希表加锁,提高了并发性能。

  • 高并发性能

    通过锁分段或更细粒度的锁机制,允许多个线程同时对不同的段或桶进行读写操作,减少了锁的竞争,提高了并发度,从而提高了性能。

7.3.4.1.3、JDK 8 的改进
  • 内部结构变化

    • JDK 8 中的 ConcurrentHashMap 采用了数组 + 链表 / 红黑树的结构,当链表长度超过阈值(默认为 8)时,将链表转换为红黑树,提高查找效率。
  • 锁机制变化

    • 摒弃了 JDK 7 的分段锁,使用 CAS 操作和 synchronized 关键字对哈希表中的单个桶进行加锁,减少了锁的粒度,提高了并发性能。
7.3.4.1.4、常见方法
方法名称功能描述
put(K key, V value)将指定的键值对存储到 ConcurrentHashMap 中,如果键已存在,则更新其值。
get(Object key)根据键获取对应的值,如果键不存在,返回 null
putIfAbsent(K key, V value)只有当键不存在时,才将键值对存储到 ConcurrentHashMap 中。
remove(Object key)移除指定键及其对应的值。
replace(K key, V oldValue, V newValue)只有当键存在且值为 oldValue 时,才将其更新为 newValue
compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)根据键和旧值计算新值,如果键不存在,oldValuenull,根据函数结果决定是否添加键值对。
7.3.4.1.5、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.ConcurrentHashMap;
​
public class ConcurrentHashMapDemo {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        // 存储键值对
        map.put("key1", 1);
        map.put("key2", 2);
        // 获取值
        System.out.println(map.get("key1"));//1
        // 检查键是否存在
        System.out.println(map.containsKey("key2"));//true
​
        // 原子操作
        map.putIfAbsent("key1", 3); // 不会更新已存在的值
        System.out.println(map.get("key1")); // 输出1
​
        // 原子更新
        map.replace("key1", 1, 3); // 仅在当前值为1时更新为3
    }
}
7.3.4.1.6、与Hashtable对比
  1. Hashtable
  • 采用synchronized关键字实现线程安全
  • 所有操作都会锁住整个表,导致并发性能差
  • JDK1.0就存在的古老实现
Hashtable<String, Integer> table = new Hashtable<>();
// 所有方法都是同步的
table.put("key1", 1);  // 锁住整个表
table.get("key1");     // 锁住整个表
  1. ConcurrentHashMap
  • 采用分段锁(JDK1.7)或CAS+synchronized(JDK1.8)实现线程安全
  • 只锁住需要修改的部分,并发性能好
  • JDK1.5引入的现代实现

对比示例:

// 性能对比示例
public class HashMapCompareExample {
    public static void main(String[] args) throws InterruptedException {
        // Hashtable测试
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        long start = System.currentTimeMillis();
        testMap(hashtable);
        System.out.println("Hashtable耗时:" + (System.currentTimeMillis() - start) + "ms");
​
        // ConcurrentHashMap测试
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        start = System.currentTimeMillis();
        testMap(concurrentMap);
        System.out.println("ConcurrentHashMap耗时:" + (System.currentTimeMillis() - start) + "ms");
    }
​
    private static void testMap(Map<String, Integer> map) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    map.put(Thread.currentThread().getName() + j, j);
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
    }
}

主要区别:

  1. 锁的粒度

    • Hashtable:所有操作都锁整个对象
    • ConcurrentHashMap:锁分段或具体的桶
  2. 并发性能

    • Hashtable:并发性能较差
    • ConcurrentHashMap:并发性能好
  3. 空值处理

    • Hashtable:不允许null键和null值
    • ConcurrentHashMap:不允许null键和null值
  4. 迭代器

    • Hashtable:迭代时不允许修改,会抛出ConcurrentModificationException
    • ConcurrentHashMap:迭代时允许修改,不会抛出异常
7.3.4.2、CopyOnWriteArrayList
7.3.4.2.1、基本概念

CopyOnWriteArrayListjava.util.concurrent 包中的一个并发容器类,它是 ArrayList 的线程安全变体。它的特点是在修改操作(如添加、删除、设置元素)时,会创建底层数组的副本,对副本进行修改,修改完成后将引用指向新数组,而不是直接在原数组上进行修改,从而实现了线程安全。

7.3.4.2.2、特点
  • 线程安全

    通过复制底层数组的方式实现线程安全,读操作不需要加锁,可以并发执行;写操作会复制整个数组,在副本上进行修改,修改完成后将原数组引用指向新数组,因此写操作相对较慢,但读操作不会阻塞,适合读多写少的场景。

  • 迭代器的弱一致性

    迭代器在创建时会持有一个指向当前数组的引用,在迭代过程中,即使原数组被修改,迭代器仍然会使用旧数组的引用,不会抛出 ConcurrentModificationException,因此是弱一致性的。

7.3.4.2.3、适用场景
  • 读多写少的场景

    • 当并发环境中读取操作远远多于修改操作时,使用 CopyOnWriteArrayList 可以提供较好的性能,因为读操作不会加锁,不会阻塞。
    • 例如,配置信息的存储,配置信息通常很少修改,但会被多个线程读取。
  • 迭代操作较多的场景

    • 由于其迭代器的弱一致性,在迭代时可以容忍并发修改,不会导致 ConcurrentModificationException,适合需要迭代操作且允许迭代过程中存在修改的场景。
7.3.4.2.4、常见方法
方法名称功能描述
add(E e)在列表末尾添加元素,会复制原数组,添加元素后更新引用。
add(int index, E element)在指定索引处添加元素,会复制原数组,添加元素后更新引用。
remove(int index)移除指定索引处的元素,复制原数组,移除元素后更新引用。
set(int index, E element)设置指定索引处的元素,复制原数组,修改元素后更新引用。
get(int index)获取指定索引处的元素,读操作不需要复制数组,可并发执行。
7.3.4.2.5、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.CopyOnWriteArrayList;
​
public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        // 添加元素
        list.add("item1");
        list.add("item2");
        list.add("item3");
​
        // 遍历元素
        for (String item : list) {
            System.out.println(item);
        }
​
        // 修改元素
        list.set(0, "newItem1");
​
        // 并发修改和读取示例
        new Thread(() -> {
            list.add("item4");
        }).start();
​
        new Thread(() -> {
            for (String item : list) {
                System.out.println(item);
            }
        }).start();
    }
}
7.3.4.2.6、注意事项
  • 内存开销

    由于每次修改操作都要复制数组,会产生额外的内存开销,在写操作频繁的场景下可能会导致内存消耗过大,因此不适合写操作频繁的情况。

  • 数据一致性延迟

    由于读操作可能读取到旧的数据副本,在对数据一致性要求非常高的场景下,可能不适用,需要根据实际情况进行权衡。

7.3.4.2.7、与 Vector 比较

Vector 是线程安全的,但使用了同步方法,性能较低,且读操作也会加锁;CopyOnWriteArrayList 的读操作不会加锁,更适合读多写少的并发场景。

7.3.4.3、ConcurrentSkipListMap
7.3.4.3.1、基本概念

ConcurrentSkipListMap 是 Java java.util.concurrent 包中的一个并发容器类,它是 SortedMap 的实现,基于跳表(Skip List)数据结构实现,支持并发操作。它提供了有序的键值对存储,并且可以在多线程环境下安全地进行插入、删除、查找等操作。

7.3.4.3.2、特点
  • 线程安全

    设计为线程安全,允许并发的读写操作,内部使用了跳表的数据结构和并发控制机制,避免了对整个映射加锁,提高了并发性能。

  • 有序性

    基于跳表实现,存储的键值对是有序的,按照键的自然顺序或自定义的 Comparator 进行排序。

7.3.4.3.3、跳表的概念
  • 跳表是一种随机化的数据结构,类似于链表,但添加了多级索引,允许快速查找、插入和删除操作,时间复杂度为 。
  • 跳表通过添加不同层次的指针,跨越多个节点,加速查找过程,类似于二分查找。
7.3.4.3.4、使用场景
  • 高并发有序映射场景

    适合多线程环境下需要存储有序键值对的场景,如缓存系统中存储有序的缓存项、任务调度系统中存储任务的优先级等。

  • 需要范围查找的场景

    由于其有序性,可以方便地进行范围查找,例如查找某个范围内的键值对,使用 subMap 方法可以轻松实现。

7.3.4.3.5、常见方法
方法名称功能描述
put(K key, V value)将指定的键值对存储到 ConcurrentSkipListMap 中,如果键已存在,则更新其值。
get(Object key)根据键获取对应的值,如果键不存在,返回 null
remove(Object key)移除指定键及其对应的值。
firstKey()返回第一个(最小)键。
lastKey()返回最后一个(最大)键。
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)返回一个子映射,键的范围在 fromKeytoKey 之间,根据 fromInclusivetoInclusive 决定是否包含边界键。
7.3.4.3.6、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.ConcurrentSkipListMap;
​
public class ConcurrentSkipListMapDemo {
    public static void main(String[] args) {
​
        ConcurrentSkipListMap<String, Integer> sortedMap = new ConcurrentSkipListMap<>();
        sortedMap.put("Z", 26);
        sortedMap.put("A", 1);
        sortedMap.put("B", 2);
​
        // 自动排序
        sortedMap.forEach((k, v) -> System.out.println(k + ": " + v));
        // 输出按键的字母顺序:A: 1, B: 2, Z: 26
    }
}
7.3.4.4、ConcurrentSkipListSet
7.3.4.4.1、基本概念

ConcurrentSkipListSet 是 Java java.util.concurrent 包中的一个并发集合类,它是 NavigableSet 的实现,基于 ConcurrentSkipListMap 实现。它提供了一个线程安全的、有序的集合,元素不允许重复,并且可以在多线程环境下进行并发的添加、删除、查找等操作。

7.3.4.4.2、特点
  • 线程安全

    基于 ConcurrentSkipListMap 实现,使用跳表数据结构和并发控制机制,确保在多线程环境下进行集合操作时的安全性,避免了对整个集合加锁,提高了并发性能。

  • 有序性

    集合中的元素是有序的,按照元素的自然顺序或自定义的 Comparator 进行排序。

7.3.4.4.3、使用场景
  • 高并发有序集合场景

    适合多线程环境下需要存储有序不重复元素的场景,如任务调度系统中存储任务的优先级、排行榜系统中存储用户的排名等。

  • 需要范围查找的场景

    可以使用 headSettailSet 等方法方便地进行范围查找,例如查找小于某个元素的元素集合或大于等于某个元素的元素集合。

7.3.4.4.4、常见方法
方法名称功能描述
add(E e)向集合中添加元素,如果元素已存在,则不添加。
contains(Object o)检查集合中是否包含指定元素。
remove(Object o)从集合中移除指定元素。
first()返回集合中的第一个(最小)元素。
last()返回集合中的最后一个(最大)元素。
headSet(E toElement)返回一个子集合,包含小于 toElement 的元素。
tailSet(E fromElement)返回一个子集合,包含大于或等于 fromElement 的元素。
7.3.4.4.5、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.ConcurrentSkipListSet;
​
public class ConcurrentSkipListSetDemo {
    public static void main(String[] args) {
        ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
        set.add("A");
        set.add("B");
        set.add("C");
​
        // 自动排序
        for (String item : set) {
            System.out.println(item);
        }
        // 输出:A, B, C
    }
}
7.3.4.5、ConcurrentLinkedQueue
7.3.4.5.1、基本概念

ConcurrentLinkedQueue 是 Java java.util.concurrent 包中的一个并发队列类,它是一个基于链表的无界队列,实现了 Queue 接口。它支持并发环境下的元素添加和移除操作,提供了高效的、线程安全的队列操作,使用非阻塞算法实现。

7.3.4.5.2、特点
  • 线程安全

    使用非阻塞算法,在多线程环境下可以安全地进行元素的添加和移除操作,避免了使用锁导致的性能开销和线程阻塞。

  • 无界队列

    队列的长度没有限制,可以不断添加元素,除非内存耗尽。

7.3.4.5.3、使用场景
  • 生产者 - 消费者模式

    适用于多线程环境下的生产者 - 消费者模式,生产者线程可以不断添加元素,消费者线程可以不断移除元素,由于使用非阻塞算法,性能较高。

  • 任务调度和线程池任务队列

    可以作为任务调度系统或线程池中的任务队列,存储待执行的任务,多个线程可以并发地添加和移除任务。

7.3.4.5.4、常见方法
方法名称功能描述
add(E e)将元素添加到队列的尾部,等同于 offer(E e) 方法,通常使用 offer 方法。
offer(E e)将元素添加到队列的尾部,是一种非阻塞的添加操作。
poll()移除并返回队首元素,如果队列为空,返回 null
peek()查看队首元素,但不移除,如果队列为空,返回 null
size()返回队列中元素的数量,但在并发环境下,该方法的结果可能不准确,因为在计算过程中元素可能被添加或移除。
7.3.4.5.5、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.ConcurrentLinkedQueue;
​
public class ConcurrentLinkedQueueDemo {
​
    public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        // 添加元素
        queue.add("item1");
        queue.offer("item2");
​
        // 支持并发迭代
        for (String item : queue) {
            System.out.println(item);
        }
​
        // 移除元素
        System.out.println(queue.poll());
        // 查看队首元素
        System.out.println(queue.peek());
        // 检查队列是否为空
        System.out.println(queue.isEmpty());
    }
}
7.3.4.6、ConcurrentLinkedDeque
7.3.4.6.1、基本概念

ConcurrentLinkedDeque 是 Java java.util.concurrent 包中的一个并发双端队列类,它实现了 Deque 接口,基于链表结构。它允许在队列的两端(头部和尾部)进行元素的添加和移除操作,并且是线程安全的,使用非阻塞算法实现,适合高并发环境。

7.3.4.6.2、特点
  • 线程安全

    使用非阻塞算法,允许在多线程环境下同时在队列的两端进行操作,避免了加锁导致的性能开销和线程阻塞,提高了并发性能。

  • 双端操作

    支持在队列的头部和尾部进行添加和移除元素操作,提供了更灵活的操作方式,例如 addFirstaddLastpollFirstpollLast 等。

7.3.4.6.3、常见方法
方法名称功能描述
addFirst(E e)在队列的头部添加元素。
addLast(E e)在队列的尾部添加元素。
offerFirst(E e)在队列的头部添加元素,是一种非阻塞的添加操作,等同于 addFirst,通常使用 offerFirst 方法。
offerLast(E e)在队列的尾部添加元素,是一种非阻塞的添加操作,等同于 addLast,通常使用 offerLast
pollFirst()移除并返回队列头部的元素,如果队列为空,返回 null
pollLast()移除并返回队列尾部的元素,如果队列为空,返回 null
peekFirst()查看队列头部的元素,但不移除,如果队列为空,返回 null
peekLast()查看队列尾部的元素,但不移除,如果队列为空,返回 null
7.3.4.6.4、代码示例
package com.wxx.juc.container;
​
import java.util.concurrent.ConcurrentLinkedDeque;
​
public class ConcurrentLinkedDequeDemo {
    public static void main(String[] args) {
        ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
        // 在头部添加元素
        deque.addFirst("item1");
        // 在尾部添加元素
        deque.addLast("item2");
​
        // 支持并发迭代
        for (String item : deque) {
            System.out.println(item);
        }
​
        // 移除头部元素
        System.out.println(deque.pollFirst());
        // 移除尾部元素
        System.out.println(deque.pollLast());
        // 查看头部元素
        System.out.println(deque.peekFirst());
        // 查看尾部元素
        System.out.println(deque.peekLast());
    }
}

7.3.5、同步辅助类

7.3.5.1、CountDownLatch

基本概念:

  • CountDownLatch 是 Java 中的一个同步辅助类,位于java.util.concurrent包中。它允许一个或多个线程等待其他线程完成操作,通过一个计数器来实现。
  • 计数器的初始值在创建 CountDownLatch 对象时指定,当一个线程完成其操作时,会调用countDown()方法将计数器减一,而等待的线程可以通过await()方法等待计数器达到零。
方法解释
public CountDownLatch(int count)参数传递线程数,表示等待线程数量
public void await()让线程等待
public void countDown()当前线程执行完毕

代码实现 :

package com.wxx.juc.syncClass;
​
import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchDemo {
    public static void main(String[] args) {
        //1.创建CountDownLatch的对象,需要传递给四个线程。
        //在底层就定义了一个计数器,此时计数器的值就是3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //2.创建四个线程对象并开启他们。
        MotherThread motherThread = new MotherThread(countDownLatch);
        motherThread.start();
​
        ChileThread1 t1 = new ChileThread1(countDownLatch);
        t1.setName("小明");
​
        ChileThread2 t2 = new ChileThread2(countDownLatch);
        t2.setName("小红");
​
        ChileThread3 t3 = new ChileThread3(countDownLatch);
        t3.setName("小刚");
​
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class ChileThread1 extends Thread {
​
    private CountDownLatch countDownLatch;
    public ChileThread1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
​
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}
​
class ChileThread2 extends Thread {
​
    private CountDownLatch countDownLatch;
    public ChileThread2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}
​
class ChileThread3 extends Thread {
​
    private CountDownLatch countDownLatch;
    public ChileThread3(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}
​
class MotherThread extends Thread {
    private CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
​
    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("妈妈在收拾碗筷");
    }
}
7.3.5.2、CyclicBarrier

基本概念:

  • CyclicBarrier 是 Java 中的一个同步辅助类,位于java.util.concurrent包中。它允许一组线程互相等待,直到到达一个公共的屏障点。
  • 当一组线程都到达这个屏障点时,会执行一个预定义的动作(可选),然后所有线程可以继续执行。
  • 与 CountDownLatch 不同的是,CyclicBarrier 可以被重置并重复使用,适用于多轮的任务同步场景。
方法名称方法解释
CyclicBarrier(int parties)构造函数,创建一个新的 CyclicBarrier,parties 表示需要等待到达屏障点的线程数量。
CyclicBarrier(int parties, Runnable barrierAction)构造函数,创建一个新的 CyclicBarrier,parties 表示需要等待到达屏障点的线程数量,barrierAction 是一个 Runnable 任务,当 parties 个线程都到达屏障点时,该任务会被执行。
await()等待直到所有线程都到达屏障点。 如果当前线程不是最后一个到达的,它会进入等待状态。 如果是最后一个到达的,它会唤醒所有等待的线程并执行预定义的动作(如果在构造函数中设置了 barrierAction)。 此方法可能会抛出 InterruptedExceptionBrokenBarrierException
await(long timeout, TimeUnit unit)在指定的时间内等待其他线程到达屏障点。 如果超时,当前线程会继续执行并抛出 TimeoutException,同时 CyclicBarrier 会进入 Broken 状态。 此方法可能会抛出 InterruptedExceptionBrokenBarrierExceptionTimeoutException
isBroken()检查 CyclicBarrier 是否处于 Broken 状态。如果有一个线程在等待时被中断、超时或在执行 barrierAction 时抛出异常,CyclicBarrier 会进入 Broken 状态。 返回 true 表示 CyclicBarrier 已损坏,返回 false 表示正常。
reset()重置 CyclicBarrier 到其初始状态,会将屏障点的计数器重置为构造函数中指定的 parties 值,并将 CyclicBarrier 的状态从 Broken 恢复到正常。 如果有线程正在等待屏障,它们将收到 BrokenBarrierException

代码实现:

package com.wxx.juc.syncClass;
​
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
​
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> System.out.println("All threads reached the barrier."));
​
        Thread thread1 = new Thread(() -> {
            try {
                System.out.println("Thread 1 is working...");
                cyclicBarrier.await();
                System.out.println("Thread 1 passed the barrier.");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
​
        Thread thread2 = new Thread(() -> {
            try {
                System.out.println("Thread 2 is working...");
                cyclicBarrier.await();
                System.out.println("Thread 2 passed the barrier.");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
​
        Thread thread3 = new Thread(() -> {
            try {
                System.out.println("Thread 3 is working...");
                cyclicBarrier.await(2, TimeUnit.SECONDS); // 等待 2 秒
                System.out.println("Thread 3 passed the barrier.");
            } catch (InterruptedException | BrokenBarrierException | java.util.concurrent.TimeoutException e) {
                e.printStackTrace();
            }
        });
​
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
7.3.5.3、Phaser

基本概念

  • Phaser 是 Java 中的一个同步辅助类,位于java.util.concurrent包中。它提供了比 CyclicBarrier 和 CountDownLatch 更灵活的多阶段同步机制,允许线程分阶段执行任务并同步。
  • 可以动态地调整参与同步的线程数量,适用于一些线程数量不固定或者在任务执行过程中需要动态增减参与者的场景。
方法名称方法解释
Phaser()构造函数,创建一个新的 Phaser,初始参与者数量为 0。
Phaser(int parties)构造函数,创建一个新的 Phaser,初始参与者数量为parties
register()注册一个新的参与者,增加参与者数量,并返回当前阶段号。
arrive()表示一个参与者已经完成了当前阶段的任务,返回当前阶段号。
arriveAndAwaitAdvance()表示一个参与者已经完成了当前阶段的任务,并等待其他参与者完成,直到所有参与者都完成当前阶段。返回下一阶段的阶段号。
arriveAndDeregister()表示一个参与者已经完成了当前阶段的任务,并注销自己,减少参与者数量。返回当前阶段号。
bulkRegister(int parties)批量注册parties个新的参与者,返回当前阶段号。
getPhase()获取当前的阶段号。
getRegisteredParties()获取当前注册的参与者数量。
isTerminated()检查 Phaser 是否已经终止。

代码实现:

package com.wxx.juc.syncClass;
​
import java.util.concurrent.Phaser;
​
public class PhaserDemo {
    public static void main(String[] args) {
        // 创建一个初始参与者数量为3的Phaser
        Phaser phaser = new Phaser(3);
​
        // 创建并启动三个线程
        new Thread(new PhaserTask(phaser, "Thread-1")).start();
        new Thread(new PhaserTask(phaser, "Thread-2")).start();
        new Thread(new PhaserTask(phaser, "Thread-3")).start();
    }
}
​
class PhaserTask implements Runnable {
    private final Phaser phaser;
    private final String name;
​
    public PhaserTask(Phaser phaser, String name) {
        this.phaser = phaser;
        this.name = name;
    }
​
    @Override
    public void run() {
        System.out.println(name + " started.");
        // 第一阶段任务
        System.out.println(name + " is working on phase 1.");
        //表示一个参与者已经完成了当前阶段的任务,并等待其他参与者完成,直到所有参与者都完成当前阶段。返回下一阶段的阶段号。
        phaser.arriveAndAwaitAdvance();
        // 第二阶段任务
        System.out.println(name + " is working on phase 2.");
        phaser.arriveAndAwaitAdvance();
        // 第三阶段任务
        System.out.println(name + " is working on phase 3.");
        phaser.arriveAndAwaitAdvance();
        // 完成任务
        System.out.println(name + " finished.");
        phaser.arriveAndDeregister();
    }
}
7.3.5.4、Semaphore

基本概念:

Semaphore 是一个计数信号量,用于控制同时访问某个特定资源的线程数量。它可以用来实现资源池、限制并发线程数等。

方法名称方法解释
Semaphore(int permits)构造函数,创建一个具有指定许可数量的 Semaphore 对象。permits 表示初始许可数量。
Semaphore(int permits, boolean fair)构造函数,创建一个具有指定许可数量的 Semaphore 对象,并可指定是否为公平模式。permits 表示初始许可数量,fairtrue 表示使用公平模式,false 表示使用非公平模式。
acquire()从信号量中获取一个许可,如果没有许可可用,将阻塞当前线程直到有许可可用。如果线程在等待许可时被中断,会抛出 InterruptedException
acquire(int permits)从信号量中获取 permits 个许可,如果没有足够的许可,将阻塞当前线程直到有足够的许可可用。如果线程在等待许可时被中断,会抛出 InterruptedException
tryAcquire()尝试获取一个许可,如果有许可可用,则获取并返回 true,否则立即返回 false,不会阻塞线程。
tryAcquire(int permits)尝试获取 permits 个许可,如果有足够的许可,则获取并返回 true,否则立即返回 false,不会阻塞线程。
tryAcquire(long timeout, TimeUnit unit)在指定的时间内尝试获取一个许可,如果有许可可用,则获取并返回 true,如果超时还未获取到许可,则返回 false。如果线程在等待许可时被中断,会抛出 InterruptedException
tryAcquire(int permits, long timeout, TimeUnit unit)在指定的时间内尝试获取 permits 个许可,如果有足够的许可,则获取并返回 true,如果超时还未获取到足够的许可,则返回 false。如果线程在等待许可时被中断,会抛出 InterruptedException
release()释放一个许可,将其添加回信号量中,使得其他等待的线程可以获取该许可。
release(int permits)释放 permits 个许可,将它们添加回信号量中,使得其他等待的线程可以获取这些许可。
availablePermits()返回当前信号量中可用的许可数量。
drainPermits()获取并返回当前信号量中所有可用的许可,将信号量中的许可数量置为零。
reducePermits(int reduction)减少信号量中的许可数量,reduction 为要减少的许可数量。
isFair()判断信号量是否使用公平模式,返回 true 表示使用公平模式,false 表示使用非公平模式。

代码实现:

package com.wxx.juc.syncClass;
​
import java.util.concurrent.Semaphore;
​
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 创建一个许可数量为2的Semaphore
        Semaphore semaphore = new Semaphore(2);
​
        // 创建并启动三个线程
        new Thread(new SemaphoreWorker(semaphore, "Thread-1")).start();
        new Thread(new SemaphoreWorker(semaphore, "Thread-2")).start();
        new Thread(new SemaphoreWorker(semaphore, "Thread-3")).start();
    }
}
​
class SemaphoreWorker implements Runnable {
    private final Semaphore semaphore;
    private final String name;
​
    public SemaphoreWorker(Semaphore semaphore, String name) {
        this.semaphore = semaphore;
        this.name = name;
    }
​
    @Override
    public void run() {
        try {
            // 请求许可
            semaphore.acquire();
            System.out.println(name + " has acquired a permit.");
            // 模拟线程的工作
            Thread.sleep((long) (Math.random() * 1000));
            // 释放许可
            semaphore.release();
            System.out.println(name + " has released a permit.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
7.3.5.5、Exchanger

基本概念:

Exchanger 是一个同步点,允许两个线程在该点交换对象,并且只有在两个线程都到达交换点时才会进行交换。

方法名称方法解释
Exchanger()构造函数,创建一个新的 Exchanger 对象。
exchange(V x)等待另一个线程到达交换点,并交换对象。 如果另一个线程已经在交换点等待,将立即执行交换并返回对方提供的对象。 如果没有其他线程等待,当前线程将阻塞直到另一个线程到达交换点。 该方法可能会抛出 InterruptedException,因为线程可能在等待交换时被中断。
exchange(V x, long timeout, TimeUnit unit)等待另一个线程到达交换点,并在指定的时间内交换对象。 如果在超时时间内另一个线程到达交换点,将执行交换并返回对方提供的对象。 如果超时,将抛出 TimeoutException 并且不会执行交换。 该方法可能会抛出 InterruptedException,因为线程可能在等待交换时被中断。

代码实现:

package com.wxx.juc.syncClass;
​
import java.util.concurrent.Exchanger;
​
public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
​
        new Thread(new ExchangerWorker(exchanger, "Thread-1", "Data from Thread-1")).start();
        new Thread(new ExchangerWorker(exchanger, "Thread-2", "Data from Thread-2")).start();
    }
}
​
class ExchangerWorker implements Runnable {
    private final Exchanger<String> exchanger;
    private final String name;
    private final String data;
​
    public ExchangerWorker(Exchanger<String> exchanger, String name, String data) {
        this.exchanger = exchanger;
        this.name = name;
        this.data = data;
    }
​
    @Override
    public void run() {
        try {
            System.out.println(name + " is waiting to exchange.");
            String exchangedData = exchanger.exchange(data);
            System.out.println(name + " received: " + exchangedData);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意:

  • 数据交换的不确定性:在多线程环境中,无法预测哪两个线程会先进行交换,这取决于线程的调度和执行顺序。
  • 可能的等待时间:如果线程数量为奇数,那么最后一个线程会一直等待,除非有新的线程加入。因此,在使用时要确保线程数量为偶数或者做好异常处理,防止最后一个线程无限等待。
  • 适用场景:Exchanger 最适合在有明确的两个线程需要交换数据的场景中使用,例如在生产者 - 消费者模式的一种变体中,生产者和消费者可以通过 Exchanger 交换数据,而不是使用共享的数据缓冲区。

7.3.6、阻塞队列-BlockingQueue接口

7.3.6.1、基本概念

BlockingQueue 是 Java java.util.concurrent 包中的一个接口,它继承自 Queue 接口,是一个阻塞队列。它提供了阻塞式的添加和移除元素的操作,在多线程环境中,当队列为空或满时,会阻塞操作线程,直到队列状态允许操作继续。

主要方法:

方法名称方法解释
put(E e)将元素添加到队列中,如果队列已满,该操作将阻塞,直到有空间可用。
take()从队列中移除并返回队首元素,如果队列为空,该操作将阻塞,直到队列中有元素。
offer(E e)将元素添加到队列中,如果队列已满,返回 false,否则返回 true
offer(E e, long timeout, TimeUnit unit)在指定的时间内尝试将元素添加到队列中,如果队列已满,会阻塞直到超时,如果成功添加,返回 true,超时返回 false
poll()从队列中移除并返回队首元素,如果队列为空,返回 null
poll(long timeout, TimeUnit unit)在指定的时间内尝试从队列中移除元素,如果队列为空,会阻塞直到超时或元素可用,超时返回 null,成功返回元素。
remainingCapacity()返回队列中还可以添加元素的数量,对于无界队列,通常返回 Integer.MAX_VALUE

接下来学习它实现类:

7.3.6.2、ArrayBlockingQueu

是一个有界阻塞队列,基于数组实现,创建时需要指定队列的容量。

package com.wxx.juc.blockingQueu;
​
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
​
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个容量为 3 的 ArrayBlockingQueue
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.put("Item 1");
        queue.put("Item 2");
        queue.put("Item 3");
        // 队列已满,此操作将阻塞
        queue.put("Item 4");
    }
}
7.3.6.3、LinkedBlockingQueue

可以是有界或无界的阻塞队列,基于链表实现。

package com.wxx.juc.blockingQueu;
​
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
​
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个无界的 LinkedBlockingQueue, 默认容量Integer.MAX_VALUE
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        queue.put("Item 1");
        queue.put("Item 2");
        // 不会阻塞,可以继续添加元素(无界情况)
        queue.put("Item 3");
    }
}
7.3.6.4、PriorityBlockingQueue

是一个无界的优先级阻塞队列,元素会根据优先级顺序出队,元素需要实现 Comparable 接口或在构造函数中提供 Comparator

package com.wxx.juc.blockingQueu;
​
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
​
public class PriorityBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
        queue.put(3);
        queue.put(1);
        queue.put(2);
        // 元素将按优先级顺序出队
        System.out.println(queue.take()); //1
    }
}
7.3.6.5、TransferQueue接口

提供了生产者直接将元素传输给消费者的功能。

LinkedTransferQueue:是 TransferQueue 的一个更通用的实现,基于链表,既可以作为普通的阻塞队列使用,也可以使用 transfer 等方法实现元素的直接传递。

方法名称方法解释
transfer(E e)传输元素给消费者,如果有消费者正在等待接收元素,元素将立即传递给它;如果没有,该方法将阻塞直到有消费者接收元素。
tryTransfer(E e)尝试传输元素给消费者,如果有消费者正在等待接收元素,元素将立即传递给它并返回 true;如果没有,立即返回 false,不阻塞。
tryTransfer(E e, long timeout, TimeUnit unit)在指定的时间内尝试传输元素给消费者,如果有消费者正在等待接收元素,元素将立即传递给它;如果没有,将阻塞直到超时或有消费者接收元素,超时返回 false,成功返回 true
hasWaitingConsumer()检查是否有消费者正在等待接收元素,如果有返回 true,否则返回 false
getWaitingConsumerCount()获取正在等待接收元素的消费者的数量。
package com.wxx.juc.blockingQueu;
​
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
​
public class TransferQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        TransferQueue<String> queue = new LinkedTransferQueue<>();
​
        // 消费者线程
        new Thread(() -> {
            try {
                System.out.println("消费者等待中!");
                String item = queue.take();
                System.out.println("消费者消费: " + item);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
​
        Thread.sleep(1000); // 确保消费者启动
​
        // 生产者线程
        new Thread(() -> {
            try {
                String item = "产品";
                System.out.println("生产者生产: " + item);
                queue.transfer(item); // 阻塞直到元素被消费
                System.out.println(item +"消费完成!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
7.3.6.3、阻塞队列实现等待唤醒机制
package com.wxx.juc.blockingQueu;
​
import java.util.concurrent.ArrayBlockingQueue;
​
public class ProductCustomDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
​
        Foodie f = new Foodie(bd);
        Cooker c = new Cooker(bd);
​
        f.start();
        c.start();
    }
}
​
class Cooker extends Thread {
​
    private ArrayBlockingQueue<String> bd;
​
    public Cooker(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                bd.put("汉堡包");
                System.out.println("厨师放入一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}
​
​
class Foodie extends Thread {
    private ArrayBlockingQueue<String> bd;
​
    public Foodie(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }
​
    @Override
    public void run() {
        while (true) {
            try {
                String take = bd.take();
                System.out.println("吃货将" + take + "拿出来吃了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}

7.3.7、Future接口

7.3.7.1、基本概念

Future 是 Java java.util.concurrent 包中的一个接口,它代表一个异步计算的结果。这个结果可能还未完成,正在进行中,或者已经完成。它提供了一些方法来检查计算是否完成,等待计算完成,以及获取计算结果。

方法名称解释
isDone()用于检查任务是否已经完成。如果任务已经完成、异常完成或被取消,返回 true,否则返回 false
get()等待计算完成并获取结果。如果任务尚未完成,该方法会阻塞调用线程,直到结果可用。如果任务抛出异常,该异常会在调用 get() 时被抛出。
get(long timeout, TimeUnit unit)等待计算完成并获取结果,但最多等待指定的时间。如果超时,会抛出 TimeoutException。如果任务抛出异常,该异常会在调用 get() 时被抛出。
cancel(boolean mayInterruptIfRunning)尝试取消任务的执行。如果任务已经完成、已经取消或由于其他原因无法取消,返回 false。如果任务正在运行且 mayInterruptIfRunningtrue,将尝试中断任务。
7.3.7.2、FutureTask

FutureTask 是 Future 接口的一个实现类,它实现了 RunnableFuture 接口,RunnableFuture 继承自 RunnableFuture。因此,它既可以作为 Runnable 任务提交给 ExecutorService 执行,也可以作为 Future 来获取结果。

package com.wxx.juc.future;
​
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
​
public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个 FutureTask 实例,传入一个 Callable 任务
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            // 模拟耗时操作
            Thread.sleep(2000);
            return 42;
        });
​
        // 启动一个线程来执行 FutureTask
        new Thread(futureTask).start();
​
        // 检查任务是否完成
        if (!futureTask.isDone()) {
            System.out.println("Task is not done yet.");
        }
​
        // 等待任务完成并获取结果
        Integer result = futureTask.get();
        System.out.println("Result: " + result);
    }
}
7.3.7.3、ScheduledFuture 接口(间接子类)

基本概念

ScheduledFuture 是一个扩展了 Future 接口的接口,主要用于表示一个延迟或定期执行的任务的结果。它通常与 ScheduledExecutorService线程池 一起使用,用于获取延迟或周期性任务的结果。

继承了 Future 接口的方法,同时可以使用 getDelay(TimeUnit unit) 方法获取任务剩余的延迟时间。

package com.wxx.juc.future;
​
import java.util.concurrent.*;
​
public class ScheduledFutureDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        // 安排一个延迟任务,延迟 1 秒执行
        ScheduledFuture<Integer> scheduledFuture = scheduledExecutorService.schedule(() -> 42, 1, TimeUnit.SECONDS);
​
        // 等待任务完成并获取结果
        Integer result = scheduledFuture.get();
        System.out.println("Result: " + result);
​
        scheduledExecutorService.shutdown();
    }
}
7.3.7.4、CompletableFuture 类

虽然 CompletableFuture 实现了 Future 接口,但它提供了更强大的功能,支持函数式编程风格,可方便地进行异步操作的组合、异常处理和并行处理。

方法名称方法解释
static <U> CompletableFuture<U> completedFuture(U value)创建一个已经完成的 CompletableFuture 对象,其结果为指定的 value,可直接获取结果,不会阻塞。
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)创建一个异步执行的 CompletableFuture 对象,使用 ForkJoinPool.commonPool() 作为默认的执行器,执行 supplier 提供的操作并获取结果。
static CompletableFuture<Void> runAsync(Runnable runnable)创建一个异步执行的 CompletableFuture 对象,使用 ForkJoinPool.commonPool() 作为默认的执行器,执行 runnable 任务,结果为 null
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)创建一个异步执行的 CompletableFuture 对象,使用指定的 executor 执行 supplier 提供的操作。
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)创建一个异步执行的 CompletableFuture 对象,使用指定的 executor 执行 runnable 任务,
thenApply(Function<? super T,? extends U> fn)CompletableFuture 完成时,将结果作为输入传递给 fn 函数进行处理,返回一个新的 CompletableFuture 对象,其结果为 fn 的处理结果。
thenAccept(Consumer<? super T> action)CompletableFuture 完成时,将结果传递给 action 进行消费,不返回新的结果,返回一个 CompletableFuture<Void>
thenRun(Runnable action)CompletableFuture 完成时,执行 action,不处理结果,不返回新的结果,返回一个 CompletableFuture<Void>
thenCompose(Function<? super T,? extends CompletableFuture<U>> fn)CompletableFuture 的结果作为输入传递给 fn 函数,fn 函数返回一个新的 CompletableFuture 对象,最终结果为新 CompletableFuture 的结果,实现异步操作的链式调用。
thenCombine(CompletableFuture<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)将当前 CompletableFuture 的结果和 other 的结果传递给 fn 函数进行处理,返回一个新的 CompletableFuture 对象,其结果为 fn 的处理结果。
allOf(CompletableFuture<?>... cfs)等待所有 CompletableFuture 对象完成,返回一个 CompletableFuture<Void>,当所有的 CompletableFuture 都完成时,该 CompletableFuture 完成。
anyOf(CompletableFuture<?>... cfs)cfs 中的任何一个 CompletableFuture 完成时,返回一个 CompletableFuture<Object>,其结果为完成的 CompletableFuture 的结果。
exceptionally(Function<Throwable,? extends T> fn)CompletableFuture 执行异常时,将异常传递给 fn 函数处理,返回一个新的 CompletableFuture 对象,其结果为 fn 的处理结果,可用于异常处理。
handle(BiFunction<? super T, Throwable,? extends U> fn)CompletableFuture 完成或异常时,将结果或异常传递给 fn 函数处理,返回一个新的 CompletableFuture 对象,其结果为 fn 的处理结果,可同时处理结果和异常。
7.3.7.4.1、supplyAsync 和 runAsync
package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // supplyAsync 示例
        CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> "Hello");
        System.out.println(supplyFuture.get());
​
        // runAsync 示例
        CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> System.out.println("Running..."));
        runFuture.get();
    }
}

代码解释

  • supplyAsync(() -> "Hello"):创建一个异步任务,返回一个包含 "Hello" 的 CompletableFuture
  • runAsync(() -> System.out.println("Running...")):创建一个异步任务,打印 "Running...",返回结果为 nullCompletableFuture
7.3.7.4.2、thenApply, thenAccept 和 thenRun
package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class CompletableFutureThenDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);
        future.thenApply(result -> result * 2)
                .thenApply(result -> result + 1)
                .thenAccept(result -> System.out.println("Result: " + result))
                .thenRun(() -> System.out.println("Task completed."));
        future.get();
    }
}
​
​

代码解释

  • supplyAsync(() -> 42):创建一个返回 42 的异步任务。
  • thenApply(result -> result * 2):将结果乘以 2。
  • thenApply(result -> result + 1):将结果加 1。
  • thenAccept(result -> System.out.println("Result: " + result)):打印最终结果。
  • thenRun(() -> System.out.println("Task completed.")):在完成后打印 "Task completed."。
7.3.7.4.3、thenCompose 和 thenCombine
package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class CompletableFutureComposeCombineDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
​
        CompletableFuture<String> composedFuture = future1.thenCompose(result1 -> CompletableFuture.supplyAsync(() -> result1 + " World"));
        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
​
        System.out.println(composedFuture.get());
        System.out.println(combinedFuture.get());
    }
}

代码解释

  • thenCompose:将第一个 CompletableFuture 的结果作为输入创建一个新的 CompletableFuture
  • thenCombine:将两个 CompletableFuture 的结果组合在一起。
7.3.7.4.4、allOf 和 anyOf
package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class CompletableFutureAllAnyDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
​
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2);
        allFuture.get();
        System.out.println(future1.get() + " " + future2.get());
​
        CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
        System.out.println(anyFuture.get());
    }
}

代码解释

  • allOf(future1, future2):等待 future1future2 都完成。
  • anyOf(future1, future2):当 future1future2 完成时,返回完成的 CompletableFuture 的结果。
7.3.7.4.5、exceptionally 和 handle
package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class CompletableFutureExceptionDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Error occurred");
            }
            return 42;
        }).exceptionally(ex -> 0);
​
        CompletableFuture<Integer> handledFuture = CompletableFuture.supplyAsync(() -> 42)
                .handle((result, ex) -> {
                    if (ex!= null) {
                        return 0;
                    }
                    return result;
                });
​
        System.out.println(future.get());
        System.out.println(handledFuture.get());
    }
}

代码解释

  • exceptionally(ex -> 0):当出现异常时,提供默认结果 0。
  • handle((result, ex) -> {...}):同时处理结果和异常,根据情况返回结果或默认值。
7.3.7.4.6、CompletableFuture 操作与线程的关系
  • 当使用 CompletableFuturesupplyAsyncrunAsync 方法时,通常会创建或使用线程来执行异步操作。
  • 这些方法会使用 ForkJoinPool.commonPool() 作为默认的执行器,除非你显式指定一个 Executor

使用自定义 Executor

你可以为 supplyAsyncrunAsync 方法提供一个自定义的 Executor,此时会使用你指定的 Executor 中的线程来执行任务。

package com.wxx.juc.future;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class CompletableFutureThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        // 使用自定义的 ExecutorService 执行异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 此代码在自定义 ExecutorService 的线程中执行
            return "Hello, World";
        }, executorService);
        System.out.println(future.get());
        executorService.shutdown();
    }
}