深入剖析Java并发工具类:线程安全、线程池、并发集合与框架实战详解

一、线程安全类

1. java.util.concurrent.atomic 包下的原子类

原子类是 Java 并发编程中非常重要的基础组件,它们能够保证在多线程环境下对变量的操作是原子性的,从而避免了线程安全问题。

(1)AtomicInteger

AtomicInteger 是对 int 类型变量进行原子操作的类。它通过利用底层的硬件指令(如 compare - and - swap,即比较并交换)来实现原子性操作。例如,我们可以通过 incrementAndGet() 方法来对一个整数进行加一操作,这个操作是原子性的,即使在多线程环境下,多个线程同时调用这个方法,也能保证最终的结果是正确的。它的实现原理是基于 Unsafe 类,Unsafe 类提供了一系列底层操作,如直接内存访问、线程调度等,而 AtomicInteger 利用 Unsafe 中的 compareAndSwapInt 方法来实现原子性。

代码示例:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);

        // 创建多个线程对 atomicInteger 进行加一操作
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                atomicInteger.incrementAndGet(); // 原子性加一
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 输出最终结果
        System.out.println("AtomicInteger 的值为:" + atomicInteger.get());
    }
}

在上述代码中,多个线程同时对 AtomicInteger 的值进行加一操作。由于 incrementAndGet() 方法是原子性的,因此最终的结果是正确的,即 AtomicInteger 的值为 1000。

(2)AtomicLong

AtomicLongAtomicInteger 类似,只不过它是对 long 类型变量进行原子操作。由于 long 类型变量占用的内存空间比 int 类型大,因此在某些情况下,对 long 类型变量的操作可能会涉及到两个机器字的读写,从而导致线程安全问题。而 AtomicLong 通过利用底层的硬件指令来保证对 long 类型变量的操作是原子性的,从而解决了这个问题。

代码示例:

import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongExample {
    public static void main(String[] args) throws InterruptedException {
        AtomicLong atomicLong = new AtomicLong(0);

        // 创建多个线程对 atomicLong 进行加一操作
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                atomicLong.incrementAndGet(); // 原子性加一
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 输出最终结果
        System.out.println("AtomicLong 的值为:" + atomicLong.get());
    }
}

在上述代码中,多个线程同时对 AtomicLong 的值进行加一操作。由于 incrementAndGet() 方法是原子性的,因此最终的结果是正确的,即 AtomicLong 的值为 1000。

(3)AtomicReference

AtomicReference 是对引用类型变量进行原子操作的类。在 Java 中,引用类型变量的赋值操作是原子性的,但是对引用类型变量所指向的对象的属性进行修改操作可能不是原子性的。例如,我们有一个对象,它有一个属性是引用类型,我们想要在多线程环境下对这个属性进行修改,如果不使用原子类,可能会出现线程安全问题。而 AtomicReference 可以保证对引用类型变量所指向的对象的属性进行修改操作是原子性的。

代码示例:

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    public static void main(String[] args) {
        AtomicReference<String> atomicReference = new AtomicReference<>("Hello");

        // 创建多个线程对 atomicReference 的值进行修改
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                atomicReference.accumulateAndGet(" World", (oldValue, newValue) -> oldValue + newValue);
            }).start();
        }

        // 输出最终结果
        System.out.println("AtomicReference 的值为:" + atomicReference.get());
    }
}

在上述代码中,多个线程同时对 AtomicReference 的值进行修改。由于 accumulateAndGet() 方法是原子性的,因此最终的结果是正确的,即所有线程的修改都被正确地应用。

2. java.util.concurrent.locks 包下的锁

锁是并发编程中用于控制多个线程对共享资源访问的一种机制。在 Java 中,提供了多种锁来满足不同的并发需求。

(1)ReentrantLock

ReentrantLock 是一种可重入的互斥锁。它与 synchronized 关键字相比,提供了更多的功能和灵活性。首先,ReentrantLock 可以尝试非阻塞地获取锁,通过 tryLock() 方法,如果当前锁没有被其他线程持有,就可以获取到锁,否则返回 false。这种方式可以避免线程在获取锁时发生死锁。其次,ReentrantLock 支持公平锁和非公平锁。公平锁是指多个线程按照它们请求锁的顺序来获取锁,先请求锁的线程会先获取到锁;非公平锁是指多个线程获取锁的顺序是随机的,可能会出现线程饥饿的情况。ReentrantLock 默认是非公平锁,但是可以通过构造函数指定为公平锁。在某些场景下,如果对线程的执行顺序有要求,就可以使用公平锁;而在对线程的执行顺序没有要求的情况下,使用非公平锁可以提高系统的吞吐量。另外,ReentrantLock 还支持锁的中断,当一个线程在等待锁的时候,可以通过调用 interrupt() 方法来中断该线程。这种方式可以避免线程在等待锁时被永久阻塞。

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lock.lock(); // 获取锁
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取了锁");
                    // 模拟业务逻辑
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock(); // 释放锁
                    System.out.println(Thread.currentThread().getName() + " 释放了锁");
                }
            }).start();
        }
    }
}

在上述代码中,多个线程通过 ReentrantLock 来获取锁。每个线程在获取锁之后,会执行一段业务逻辑,然后释放锁。由于 ReentrantLock 是互斥锁,因此同一时间只有一个线程可以获取到锁,从而保证了线程安全。

(2)ReadWriteLock

ReadWriteLock 是一种读写锁。它允许多个线程同时读取共享资源,但是写操作是互斥的。这种方式可以提高系统的并发性能,因为在大多数情况下,读操作的频率要远高于写操作。例如,在一个数据库缓存系统中,多个线程可能会同时读取缓存中的数据,但是写操作相对较少。如果使用普通的互斥锁,每次只有一个线程可以访问缓存,这会导致系统的并发性能很低。而使用 ReadWriteLock,多个线程可以同时读取缓存中的数据,只有当有线程要写入数据时,才会阻塞其他线程。

代码示例:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static int value = 0;

    public static void main(String[] args) {
        // 创建多个读线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                readWriteLock.readLock().lock(); // 获取读锁
                try {
                    System.out.println(Thread.currentThread().getName() + " 读取了值:" + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readWriteLock.readLock().unlock(); // 释放读锁
                }
            }, "Reader-" + i).start();
        }

        // 创建一个写线程
        new Thread(() -> {
            readWriteLock.writeLock().lock(); // 获取写锁
            try {
                System.out.println(Thread.currentThread().getName() + " 开始写入");
                value = 100;
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " 写入完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock(); // 释放写锁
            }
        }, "Writer").start();
    }
}

在上述代码中,多个读线程通过 readWriteLock.readLock() 获取读锁,可以同时读取共享变量 value 的值;而写线程通过 readWriteLock.writeLock() 获取写锁,写锁是互斥的,因此写线程在写入数据时会阻塞其他线程的读取操作。这种方式可以提高系统的并发性能。

二、线程池类

线程池是一种用于管理和复用线程的机制。它可以避免频繁地创建和销毁线程所带来的开销,提高系统的性能。

1. ThreadPoolExecutor

ThreadPoolExecutor 是 Java 中最常用的线程池类。它提供了丰富的配置参数,可以满足不同的并发需求。首先,ThreadPoolExecutor 可以设置核心线程数和最大线程数。核心线程数是指线程池中始终保持活动的线程数量,即使这些线程处于空闲状态,也不会被销毁;最大线程数是指线程池中允许的最大线程数量,当任务队列满了之后,如果还有新的任务到来,就会创建新的线程,直到达到最大线程数。通过合理地设置核心线程数和最大线程数,可以充分利用系统的资源,提高系统的性能。其次,ThreadPoolExecutor 支持多种任务队列。任务队列用于存放等待执行的任务,常见的任务队列有 ArrayBlockingQueueLinkedBlockingQueue 等。ArrayBlockingQueue 是一个基于数组的阻塞队列,它的容量是固定的,当队列满了之后,就会阻塞线程池中的线程;LinkedBlockingQueue 是一个基于链表的阻塞队列,它的容量可以是无界的,也可以是有限的。通过选择合适的任务队列,可以控制线程池的行为。另外,ThreadPoolExecutor 还提供了多种拒绝策略。当线程池满了之后,如果还有新的任务到来,就会执行拒绝策略。常见的拒绝策略有 AbortPolicyCallerRunsPolicy 等。AbortPolicy 是直接抛出异常,拒绝任务;CallerRunsPolicy 是让调用线程自己执行任务。通过选择合适的拒绝策略,可以控制线程池在满的时候的行为。

代码示例:

import java.util.concurrent.*;

public class ThreadPoolExecutorExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2, // 核心线程数
                4, // 最大线程数
                60, // 空闲线程存活时间
                TimeUnit.SECONDS, // 时间单位
                new LinkedBlockingQueue<>(10), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交任务
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPoolExecutor.submit(() -> {
                System.out.println("任务 " + finalI + " 开始执行,线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + finalI + " 执行完成,线程:" + Thread.currentThread().getName());
            });
        }

        // 关闭线程池
        threadPoolExecutor.shutdown();
        threadPoolExecutor.awaitTermination(1, TimeUnit.MINUTES);
    }
}

在上述代码中,我们创建了一个线程池,核心线程数为 2,最大线程数为 4,任务队列的容量为 10。我们提交了 10 个任务,每个任务的执行时间约为 2 秒。线程池会根据任务的到达情况动态地创建线程,直到达到最大线程数。如果任务队列满了且线程池也满了,就会执行拒绝策略。

2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 是一种用于执行定时任务的线程池。它继承自 ThreadPoolExecutor,因此也具备 ThreadPoolExecutor 的所有功能。ScheduledThreadPoolExecutor 可以在给定的延迟之后执行任务,也可以周期性地执行任务。例如,我们可以使用 ScheduledThreadPoolExecutor 来定期清理系统中的垃圾文件。通过设置延迟时间和周期时间,ScheduledThreadPoolExecutor 可以按照我们设定的时间间隔来执行任务。

代码示例:

import java.util.concurrent.*;

public class ScheduledThreadPoolExecutorExample {
    public static void main(String[] args) {
        // 创建一个 ScheduledThreadPoolExecutor
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);

        // 提交一个延迟任务
        scheduledThreadPoolExecutor.schedule(() -> {
            System.out.println("延迟任务执行,线程:" + Thread.currentThread().getName());
        }, 5, TimeUnit.SECONDS);

        // 提交一个周期性任务
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            System.out.println("周期性任务执行,线程:" + Thread.currentThread().getName());
        }, 0, 2, TimeUnit.SECONDS);

        // 防止主线程退出
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 关闭线程池
        scheduledThreadPoolExecutor.shutdown();
    }
}

在上述代码中,我们创建了一个 ScheduledThreadPoolExecutor,核心线程数为 2。我们提交了一个延迟任务,延迟 5 秒后执行;同时提交了一个周期性任务,每隔 2 秒执行一次。ScheduledThreadPoolExecutor 会根据设定的时间间隔来执行任务。

三、并发集合类

并发集合类是 Java 中用于存储和操作数据的线程安全的集合类。它们在多线程环境下可以保证数据的一致性和完整性。

1. ConcurrentHashMap

ConcurrentHashMap 是一种线程安全的哈希表。它与传统的 HashMap 不同,HashMap 在多线程环境下可能会出现线程安全问题,例如,当多个线程同时对 HashMap 进行修改操作时,可能会导致数据丢失或者程序崩溃。而 ConcurrentHashMap 通过分段锁来实现线程安全。它将整个哈希表分成多个段,每个段都有自己的锁。当一个线程对一个段进行操作时,不会影响到其他段的操作。这种方式可以提高系统的并发性能,因为多个线程可以同时对不同的段进行操作。

代码示例:

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

        // 创建多个线程对 concurrentHashMap 进行操作
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                concurrentHashMap.put("key-" + finalI, finalI);
                System.out.println("线程 " + Thread.currentThread().getName() + " 插入了键值对:" + "key-" + finalI + " -> " + finalI);
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 输出最终结果
        System.out.println("ConcurrentHashMap 的内容为:" + concurrentHashMap);
    }
}

在上述代码中,多个线程同时对 ConcurrentHashMap 进行插入操作。由于 ConcurrentHashMap 是线程安全的,因此最终的结果是正确的,所有键值对都被正确地插入。

2. ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一种线程安全的无界队列。它基于链表实现,支持高并发的入队和出队操作。在多线程环境下,多个线程可以同时对 ConcurrentLinkedQueue 进行入队和出队操作,而不会出现线程安全问题。ConcurrentLinkedQueue 的实现原理是基于原子操作。它使用原子变量来维护队列的头节点和尾节点,当一个线程对队列进行入队操作时,它会通过原子操作将新节点插入到队列的尾部;当一个线程对队列进行出队操作时,它会通过原子操作将队列的头节点移除。

代码示例:

import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueExample {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();

        // 创建多个线程对 concurrentLinkedQueue 进行入队操作
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                concurrentLinkedQueue.offer("元素-" + finalI);
                System.out.println("线程 " + Thread.currentThread().getName() + " 入队了元素:" + "元素-" + finalI);
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 创建多个线程对 concurrentLinkedQueue 进行出队操作
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                String element = concurrentLinkedQueue.poll();
                System.out.println("线程 " + Thread.currentThread().getName() + " 出队了元素:" + element);
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 输出最终结果
        System.out.println("ConcurrentLinkedQueue 的内容为:" + concurrentLinkedQueue);
    }
}

在上述代码中,多个线程同时对 ConcurrentLinkedQueue 进行入队操作,然后多个线程同时对 ConcurrentLinkedQueue 进行出队操作。由于 ConcurrentLinkedQueue 是线程安全的,因此最终的结果是正确的,所有元素都被正确地入队和出队。

3. CopyOnWriteArrayList

CopyOnWriteArrayList 是一种线程安全的动态数组。它的名字来源于它的实现原理,即写入时复制。当对 CopyOnWriteArrayList 进行修改操作(如添加、删除等)时,它会先创建一个新的数组,将原数组中的元素复制到新数组中,然后对新数组进行修改操作,最后将原数组指向新数组。这种方式可以保证在修改操作期间,其他线程可以正常地读取原数组,从而避免了线程安全问题。CopyOnWriteArrayList 适用于读多写少的场景,因为在写操作时需要创建一个新的数组,所以写操作的性能比较低,但是读操作的性能比较高。

代码示例:

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

        // 创建多个线程对 copyOnWriteArrayList 进行添加操作
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                copyOnWriteArrayList.add("元素-" + finalI);
                System.out.println("线程 " + Thread.currentThread().getName() + " 添加了元素:" + "元素-" + finalI);
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 创建多个线程对 copyOnWriteArrayList 进行读取操作
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (String element : copyOnWriteArrayList) {
                    System.out.println("线程 " + Thread.currentThread().getName() + " 读取了元素:" + element);
                }
            }).start();
        }

        // 等待所有线程完成
        Thread.sleep(1000);

        // 输出最终结果
        System.out.println("CopyOnWriteArrayList 的内容为:" + copyOnWriteArrayList);
    }
}

在上述代码中,多个线程同时对 CopyOnWriteArrayList 进行添加操作,然后多个线程同时对 CopyOnWriteArrayList 进行读取操作。由于 CopyOnWriteArrayList 是线程安全的,因此最终的结果是正确的,所有元素都被正确地添加和读取。

四、并发工具类

并发工具类是一些用于简化并发编程的工具类,它们提供了一些常用的并发操作。

1. CountDownLatch

CountDownLatch 是一种倒计时的同步工具类。它允许一个或多个线程等待其他线程完成操作。例如,在一个分布式系统中,多个线程可能会同时执行任务,但是主线程需要等待所有子线程完成任务之后才能继续执行。使用 CountDownLatch,主线程可以创建一个 CountDownLatch 对象,并设置倒计时的数目为子线程的数量。每个子线程在完成任务之后,都会调用 CountDownLatchcountDown() 方法,减少倒计时的数目。当倒计时的数目为零时,主线程就可以继续执行。

代码示例:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 5;
        CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads);

        // 创建多个子线程
        for (int i = 0; i < numberOfThreads; i++) {
            int finalI = i;
            new Thread(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 开始执行任务");
                try {
                    Thread.sleep(1000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 " + Thread.currentThread().getName() + " 完成任务");
                countDownLatch.countDown(); // 任务完成,倒计时减一
            }, "Thread-" + finalI).start();
        }

        // 主线程等待所有子线程完成任务
        System.out.println("主线程等待所有子线程完成任务");
        countDownLatch.await();
        System.out.println("所有子线程已完成任务,主线程继续执行");
    }
}

在上述代码中,主线程创建了一个 CountDownLatch 对象,倒计时数目为 5。主线程调用 countDownLatch.await() 方法等待所有子线程完成任务。每个子线程在完成任务后调用 countDownLatch.countDown() 方法,减少倒计时数目。当倒计时数目为零时,主线程继续执行。

2. CyclicBarrier

CyclicBarrier 是一种可循环使用的同步工具类。它允许一组线程到达一个屏障点之后,再一起继续执行。例如,在一个并行计算的场景中,多个线程可能会同时计算一个任务的不同部分,但是每个线程在完成自己的部分之后,都需要等待其他线程也完成自己的部分,然后一起继续执行。使用 CyclicBarrier,每个线程在完成自己的部分之后,都会调用 CyclicBarrierawait() 方法,等待其他线程也到达屏障点。当所有线程都到达屏障点之后,它们就可以一起继续执行。

代码示例:

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int numberOfThreads = 5;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(numberOfThreads);

        // 创建多个线程
        for (int i = 0; i < numberOfThreads; i++) {
            int finalI = i;
            new Thread(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 开始执行任务");
                try {
                    Thread.sleep(1000 * finalI); // 模拟任务执行时间
                    System.out.println("线程 " + Thread.currentThread().getName() + " 到达屏障点");
                    cyclicBarrier.await(); // 等待其他线程到达屏障点
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("线程 " + Thread.currentThread().getName() + " 继续执行");
            }, "Thread-" + finalI).start();
        }
    }
}

在上述代码中,我们创建了一个 CyclicBarrier 对象,屏障点的数目为 5。每个线程在完成任务后调用 cyclicBarrier.await() 方法,等待其他线程到达屏障点。当所有线程都到达屏障点之后,它们可以一起继续执行。

3. Semaphore

Semaphore 是一种信号量的同步工具类。它允许控制同时访问某个资源的线程数量。例如,在一个数据库连接池中,我们希望同时只有一定数量的线程可以获取数据库连接。使用 Semaphore,我们可以创建一个信号量对象,并设置信号量的数量为数据库连接池的大小。当一个线程需要获取数据库连接时,它会调用 Semaphoreacquire() 方法,减少信号量的数目;当一个线程释放数据库连接时,它会调用 Semaphorerelease() 方法,增加信号量的数目。通过这种方式,Semaphore 可以控制同时访问数据库连接池的线程数量。

代码示例:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        int numberOfPermits = 3; // 信号量的数量
        Semaphore semaphore = new Semaphore(numberOfPermits);

        // 创建多个线程
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 获取信号量
                    System.out.println("线程 " + Thread.currentThread().getName() + " 获得了信号量,开始执行任务");
                    Thread.sleep(2000); // 模拟任务执行时间
                    System.out.println("线程 " + Thread.currentThread().getName() + " 完成任务,释放信号量");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放信号量
                }
            }, "Thread-" + finalI).start();
        }
    }
}

在上述代码中,我们创建了一个 Semaphore 对象,信号量的数量为 3。每个线程在执行任务前调用 semaphore.acquire() 方法获取信号量,任务完成后调用 semaphore.release() 方法释放信号量。通过这种方式,Semaphore 可以控制同时访问资源的线程数量。

五、并发框架类

并发框架类是一些用于构建并发程序的框架类,它们提供了一些常用的并发模式。

1. ForkJoinPool

ForkJoinPool 是一种用于执行分治任务的线程池。它允许一个任务被分解成多个子任务,然后并行地执行这些子任务,最后将子任务的结果合并起来。例如,在一个大数据处理的场景中,一个任务可能需要处理大量的数据,我们可以将这个任务分解成多个子任务,每个子任务处理一部分数据,然后将子任务的结果合并起来。使用 ForkJoinPool,我们可以创建一个 ForkJoinPool 对象,并提交一个分治任务。ForkJoinPool 会将任务分解成多个子任务,并将子任务分配给线程池中的线程来执行。当所有子任务都执行完成之后,ForkJoinPool 会将子任务的结果合并起来。

代码示例:

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

public class ForkJoinPoolExample {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // 创建一个分治任务
        ForkJoinTask<Integer> task = new SumTask(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10);

        // 提交任务到 ForkJoinPool
        Integer result = forkJoinPool.invoke(task);

        System.out.println("计算结果为:" + result);
    }

    // 自定义分治任务
    static class SumTask extends RecursiveTask<Integer> {
        private int[] array;
        private int start;
        private int end;

        public SumTask(int[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            if (end - start <= 3) { // 如果任务足够小,直接计算
                int sum = 0;
                for (int i = start; i < end; i++) {
                    sum += array[i];
                }
                return sum;
            } else { // 否则分解任务
                int middle = (start + end) / 2;
                SumTask leftTask = new SumTask(array, start, middle);
                SumTask rightTask = new SumTask(array, middle, end);

                leftTask.fork(); // 异步执行左子任务
                int rightResult = rightTask.compute(); // 同步执行右子任务
                int leftResult = leftTask.join(); // 等待左子任务完成并获取结果

                return leftResult + rightResult;
            }
        }
    }
}

在上述代码中,我们定义了一个分治任务 SumTask,用于计算数组的和。SumTask 继承自 RecursiveTask,并重写了 compute() 方法。在 compute() 方法中,如果任务足够小,直接计算结果;否则,将任务分解为两个子任务,分别计算左半部分和右半部分的和,最后将两个子任务的结果合并。我们创建了一个 ForkJoinPool 对象,并提交了分治任务。ForkJoinPool 会自动分解任务并并行执行子任务,最终返回计算结果。

2. FutureTask

FutureTask 是一种用于表示异步计算结果的类。它允许一个线程异步地执行一个任务,而其他线程可以等待这个任务的结果。例如,在一个 Web 应用程序中,一个请求可能需要调用多个服务来获取数据,我们可以将每个服务的调用封装成一个任务,并使用 FutureTask 来异步地执行这些任务。其他线程可以等待这些任务的结果,当所有任务都完成之后,就可以将结果返回给用户。

代码示例:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class FutureTaskExample {
    public static void main(String[] args) throws Exception {
        // 创建一个 Callable 任务
        Callable<Integer> callable = () -> {
            System.out.println("任务开始执行,线程:" + Thread.currentThread().getName());
            Thread.sleep(2000); // 模拟任务执行时间
            System.out.println("任务执行完成,线程:" + Thread.currentThread().getName());
            return 42; // 返回任务结果
        };

        // 创建一个 FutureTask 对象
        FutureTask<Integer> futureTask = new FutureTask<>(callable);

        // 创建一个线程来执行 FutureTask
        new Thread(futureTask).start();

        // 主线程等待任务完成并获取结果
        System.out.println("主线程等待任务完成");
        Integer result = futureTask.get(); // 阻塞等待任务完成
        System.out.println("任务结果为:" + result);
    }
}

在上述代码中,我们定义了一个 Callable 任务,任务的执行时间为 2 秒,返回结果为 42。我们创建了一个 FutureTask 对象,并将 Callable 任务传递给它。然后我们创建了一个线程来执行 FutureTask。主线程通过调用 futureTask.get() 方法等待任务完成并获取结果。futureTask.get() 方法会阻塞主线程,直到任务完成。

posted @ 2025-04-09 11:26  软件职业规划  阅读(26)  评论(0)    收藏  举报
相关博文:
阅读排行:
· 一则复杂 SQL 改写后有感
· 接口被刷百万QPS,怎么防?
· C# 锁机制全景与高效实践:从 Monitor 到 .NET 9 全新 Lock
· 一个开源免费、功能丰富的 WPF 自定义控件资源库
· 提升Avalonia UI质感,跨平台图标库选型实践
点击右上角即可分享
微信分享提示