首页 大数据

Java 并发编程利器:并发工具类面试通关指南(附生活案例)

分类:大数据
字数: (6985)
阅读: (2087)
内容摘要:Java 并发编程利器:并发工具类面试通关指南(附生活案例),

在 Java 并发编程中,java.util.concurrent 包下的并发工具类是面试的常客,也是实际开发中解决高并发问题的利器。面对这些工具类,死记硬背 API 是远远不够的。我们需要理解其背后的原理,才能在面试和实际应用中游刃有余。本文将结合生活案例,深入剖析常见的 Java 并发工具类,助你轻松应对面试。

1. CountDownLatch:倒计时器,生活中的“发令枪”

CountDownLatch 允许一个或多个线程等待其他线程完成操作。可以将它想象成运动会上的发令枪。主线程(裁判)等待所有运动员(子线程)准备就绪后,才能开始比赛。

底层原理

CountDownLatch 内部维护一个计数器,该计数器初始化为一个正整数。当一个线程调用 countDown() 方法时,计数器减 1。当计数器变为 0 时,所有等待的线程都被释放。await() 方法用于阻塞当前线程,直到计数器变为 0。

代码示例

Java 并发编程利器:并发工具类面试通关指南(附生活案例)
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 3; // 模拟 3 个线程
        CountDownLatch latch = new CountDownLatch(numberOfThreads); // 初始化计数器
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            final int threadId = i + 1;
            executor.submit(() -> {
                try {
                    System.out.println("线程 " + threadId + " 正在准备...");
                    Thread.sleep((long) (Math.random() * 3000)); // 模拟准备时间
                    System.out.println("线程 " + threadId + " 准备完毕!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 计数器减 1
                }
            });
        }

        latch.await(); // 主线程等待所有子线程完成
        System.out.println("所有线程准备完毕,开始执行!");
        executor.shutdown(); // 关闭线程池
    }
}

避坑经验

  • 确保每个线程都会调用 countDown() 方法,否则主线程会一直阻塞。
  • 如果计数器的初始值为 0,则 await() 方法会立即返回,不会阻塞。

2. CyclicBarrier:循环栅栏,团队协作的“集合点”

CyclicBarrier 允许一组线程互相等待,直到所有线程都到达一个公共屏障点。可以将它想象成团队协作中的集合点,每个成员到达后,才能一起开始下一步工作。

底层原理

CyclicBarrier 内部维护一个计数器,初始值为 parties(参与线程的数量)。当一个线程调用 await() 方法时,计数器减 1。当计数器变为 0 时,所有等待的线程都被释放,并执行 barrierAction(可选)。之后,计数器重置为 parties,进入下一个循环。

Java 并发编程利器:并发工具类面试通关指南(附生活案例)

代码示例

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierExample {

    public static void main(String[] args) {
        int numberOfThreads = 3; // 模拟 3 个线程
        CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
            System.out.println("所有线程到达栅栏,开始下一步操作!");
        }); // 初始化 CyclicBarrier,并设置 barrierAction
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            final int threadId = i + 1;
            executor.submit(() -> {
                try {
                    System.out.println("线程 " + threadId + " 正在执行第一阶段...");
                    Thread.sleep((long) (Math.random() * 3000)); // 模拟执行时间
                    System.out.println("线程 " + threadId + " 第一阶段执行完毕,等待其他线程...");
                    barrier.await(); // 等待其他线程到达栅栏

                    System.out.println("线程 " + threadId + " 正在执行第二阶段...");
                    Thread.sleep((long) (Math.random() * 3000)); // 模拟执行时间
                    System.out.println("线程 " + threadId + " 第二阶段执行完毕!");
                    barrier.await(); // 等待其他线程到达栅栏,进入下一次循环

                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

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

避坑经验:

  • 注意处理 BrokenBarrierException 异常,该异常表示其中一个线程中断,导致所有等待线程都无法继续执行。
  • CyclicBarrier 可以重用,而 CountDownLatch 只能使用一次。

3. Semaphore:信号量,停车场的“停车位”

Semaphore 用于控制同时访问特定资源的线程数量。可以将它想象成停车场的停车位。每个线程需要获取一个许可(停车位),才能访问资源。当线程释放许可时,其他等待的线程才能获取许可。

底层原理

Java 并发编程利器:并发工具类面试通关指南(附生活案例)

Semaphore 内部维护一个计数器,表示可用许可的数量。acquire() 方法尝试获取许可,如果计数器大于 0,则计数器减 1,线程继续执行;否则,线程阻塞,直到有其他线程释放许可。release() 方法释放许可,计数器加 1,并唤醒一个等待的线程。

代码示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreExample {

    public static void main(String[] args) {
        int numberOfThreads = 5; // 模拟 5 个线程
        int permits = 2; // 模拟 2 个停车位
        Semaphore semaphore = new Semaphore(permits); // 初始化 Semaphore
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfThreads; i++) {
            final int threadId = i + 1;
            executor.submit(() -> {
                try {
                    semaphore.acquire(); // 获取许可,相当于进入停车场
                    System.out.println("线程 " + threadId + " 获取许可,开始访问资源...");
                    Thread.sleep((long) (Math.random() * 3000)); // 模拟访问资源的时间
                    System.out.println("线程 " + threadId + " 访问资源完毕,释放许可!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放许可,相当于离开停车场
                }
            });
        }

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

避坑经验:

  • 确保 acquire()release() 方法成对出现,否则可能会导致许可泄漏。
  • 可以使用 tryAcquire() 方法尝试获取许可,如果获取不到则立即返回,避免阻塞。

4. Exchanger:交换器,相亲的“红娘”

Exchanger 允许两个线程交换数据。可以将它想象成相亲的红娘,她负责将男女双方的信息进行交换。

Java 并发编程利器:并发工具类面试通关指南(附生活案例)

底层原理

Exchanger 内部维护一个槽位,用于存储一个线程传递的数据。当第一个线程调用 exchange() 方法时,它将数据放入槽位,并阻塞等待第二个线程。当第二个线程调用 exchange() 方法时,它将自己的数据与槽位中的数据进行交换,并将交换后的数据返回给第一个线程。两个线程都被唤醒。

代码示例

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerExample {

    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        ExecutorService executor = Executors.newFixedThreadPool(2);

        executor.submit(() -> {
            String data = "线程 1 的数据";
            try {
                System.out.println("线程 1 准备交换数据:" + data);
                String exchangedData = exchanger.exchange(data);
                System.out.println("线程 1 交换后的数据:" + exchangedData);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executor.submit(() -> {
            String data = "线程 2 的数据";
            try {
                System.out.println("线程 2 准备交换数据:" + data);
                String exchangedData = exchanger.exchange(data);
                System.out.println("线程 2 交换后的数据:" + exchangedData);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executor.shutdown();
    }
}

避坑经验:

  • Exchanger 只能用于两个线程之间的数据交换。
  • 如果一个线程等待时间过长,可能会抛出 TimeoutException 异常。

掌握了这些 Java 并发工具类的原理和使用方法,相信你就能在面试中脱颖而出,并在实际开发中编写出更加高效、稳定的并发程序。记住,理解底层原理比死记硬背 API 更重要。

Java 并发编程利器:并发工具类面试通关指南(附生活案例)

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea4.store/blog/288928.SHTML

本文最后 发布于2026-04-13 03:14:48,已经过了14天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • e人代表 3 天前
    CyclicBarrier 这部分写得太棒了,集合点的比喻很形象!