并发设计模式

2.1 两阶段终止线程(优雅终止线程)
  • 问题背景
    • 多线程中如何安全终止一个线程?错误方式如stop()会导致死锁,destroy()会终止整个进程。
  • 正确方式:两阶段终止模式
    • 第一阶段:发送中断请求
      • 使用interrupt()方法设置中断标志位。
      • 可唤醒处于休眠状态的线程。
    • 第二阶段:判断中断标志位并处理收尾逻辑
      • 在线程内检查Thread.interrupted()标志。
      • 若为true,则执行资源释放等操作后结束线程。
    • 注意点
      • 中断异常处理需重新设置中断标志位,防止标志被清除。
      • 结合变量标志位使用更可靠。
2.2 避免共享的设计模式
2.2.1 不变性模式
java 复制代码
// 1. 类 final
public final class ImmutableUser {

    // 2. 属性 private final
    private final String name;
    private final int age;

    // 3. 构造器一次性赋值
    public ImmutableUser(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 4. 只有 getter,没有 setter
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
  • 思想
    • 对象创建后不可修改,只读对象天然线程安全。
  • 实现方式
    • 类和属性用final修饰。
    • 去除setter方法,仅保留getter。
  • 适用场景
    • 缓存数据、配置信息、操作系统只读数据。
  • 注意事项
    • 可变对象即使属性为final也无法保证线程安全。
    • 应避免暴露内部可变状态。
2.2.2 写实复制(Copy-on-Write)
java 复制代码
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;

// 手写极简版 CopyOnWriteArrayList
public class SimpleCopyOnWriteList<E> {

    // 真正存数据的数组,volatile 保证可见性
    private volatile Object[] array;

    // 写操作加锁,防止多线程同时复制
    private final ReentrantLock lock = new ReentrantLock();

    // 构造:空数组
    public SimpleCopyOnWriteList() {
        array = new Object[0];
    }

    // ==================== 读方法:不加锁,直接读 ====================
    public E get(int index) {
        return (E) array[index];
    }

    public int size() {
        return array.length;
    }

    // ==================== 写方法:加锁 + 复制新数组 ====================
    public boolean add(E e) {
        lock.lock();
        try {
            // 1. 获取旧数组
            Object[] oldArr = array;

            // 2. 复制一个新数组,长度+1
            Object[] newArr = Arrays.copyOf(oldArr, oldArr.length + 1);

            // 3. 在新数组上添加元素
            newArr[oldArr.length] = e;

            // 4. 把引用指向新数组(volatile 保证其他线程立刻看到)
            array = newArr;

            return true;
        } finally {
            lock.unlock();
        }
    }
}
  • 原理
    • 修改对象时不直接修改原对象,而是复制一份再修改。
  • 典型应用
    • CopyOnWriteArrayList在写操作时复制数组。
    • 操作系统fork进程时也采用该机制。
  • 优点
    • 适用于读多写少场景。
  • 缺点
    • 内存占用高。
2.2.3 线程本地存储(ThreadLocal)
java 复制代码
public class ThreadLocalTest {

    // 定义 ThreadLocal
    private static final ThreadLocal<Integer> TL = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {

        // 线程1 +10
        new Thread(() -> {
            TL.set(TL.get() + 10);
            System.out.println("线程1:" + TL.get()); // 10
        }).start();

        // 线程2 +20
        new Thread(() -> {
            TL.set(TL.get() + 20);
            System.out.println("线程2:" + TL.get()); // 20
        }).start();
    }
}
  • 作用
    • 每个线程拥有独立副本,避免线程间竞争。
  • 典型应用
    • 链路追踪上下文、全局事务ID传递、游戏玩家状态管理。
  • 注意事项
    • 线程池中使用ThreadLocal需及时调用remove(),否则可能导致内存泄漏。
2.3 多线程协作的设计模式
2.3.1 首次挂起(等待唤醒机制)
  • 实现方式
    • Java中使用wait()/notify()ReentrantLock+Condition
    • 使用CAS+park/unpark实现高性能无锁协作。
  • 典型应用
    • 生产者消费者模型。
    • 同步任务依赖,如等待某个结果后再继续执行。
2.3.2 Become模式(避免重复执行)
  • 思想
    • 多个线程竞争执行某项任务时,仅允许一个线程完成任务,其他线程跳过。
  • 典型应用
    • 单例双重校验锁(DCL)。
    • 锁膨胀过程中多个线程竞争初始化monitor对象。
    • 注册中心组件初始化、定时任务启动。
2.4 多线程分工模式
2.4.1 Thread-per-Message(每个任务一个线程)
  • 特点
    • 每个任务分配一个线程,简单但线程开销大。
  • 典型应用
    • BIO网络编程中为每个连接分配一个线程。
    • 定时任务中单独线程执行任务。
2.4.2 Worker Thread(工作线程池)
  • 优势
    • 避免频繁创建销毁线程,提高性能。
    • 支持任务队列缓冲,适应突发流量。
  • 典型应用
    • Java线程池(ThreadPoolExecutor)。
    • Netty事件循环组。
    • 分布式注册中心心跳检测线程池。
2.4.3 生产者消费者模式
  • 核心思想
    • 生产者提交任务,消费者异步处理,解耦生产与消费过程。
  • 典型应用
    • 异步日志记录。
    • 用户注册后异步发邮件/短信。
    • 订单系统解耦库存扣减。
  • 优势
    • 提升接口响应速度。
    • 实现服务降级。
    • 削峰填谷,平滑处理高峰流量。
  • 关键考量
    • 消费者数量不足 → 积压。
    • 队列容量不足 → 需扩容。
    • 生产速度远超消费速度 → 限流。

3. 并发总结与面试指导

3.1 并发设计模式的重要性
  • 并发设计模式是对常见并发问题的经验抽象与方案归纳。
  • 能帮助开发者快速选择合适的解决方案,提升代码质量与稳定性。
3.2 面试高频考点
  • 理论基础
    • 并发与并行区别。
    • 线程与进程理解。
  • 线程通信机制
    • wait()/notify()ThreadLocal、中断机制。
  • 锁机制
    • synchronizedReentrantLockCondition
    • AQS原理、锁升级流程(偏向锁→轻量级锁→重量级锁)。
  • JMM模型
    • happens-before原则、可见性、原子性、有序性。
    • MESI协议、伪共享。
  • 工具类与容器
    • ConcurrentHashMapCopyOnWriteArrayList
    • CountDownLatchCyclicBarrierSemaphore
    • FutureCompletableFutureForkJoinPool
  • 线程池
    • 核心参数、拒绝策略、工作队列作用。
    • 如何合理配置线程池以应对不同业务场景。
相关推荐
DynamicsAgg2 小时前
企业数字化底座-k8s企业实践系列第二篇pod创建调度
java·容器·kubernetes
222you2 小时前
四个主要的函数式接口
java·开发语言
Javatutouhouduan3 小时前
Java全栈面试进阶宝典:内容全面,题目高频!
java·高并发·java面试·java面试题·后端开发·java程序员·java八股文
u0136863823 小时前
将Python Web应用部署到服务器(Docker + Nginx)
jvm·数据库·python
SEO-狼术3 小时前
RAD Studio 13.1 Florence adds
java
ywf12153 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
smchaopiao3 小时前
Python中字典与列表合并的问题与解决方法
开发语言·python
敲代码的瓦龙4 小时前
Java?面向对象三大特性!!!
java·开发语言