Java并发编程

一、线程的基础概念

1.1 基本概念

1.1.1 进程与线程

进程: 进程是指运行中的程序。 比如我们使用钉钉、浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源);

所谓进程就是线程的容器,需要线程利用进程中的一些资源,处理一个代码、指令。最终实现进程锁预期的结果。

线程: 线程是CPU调度的基本单位,每个线程执行的都是某一个进程的代码的某个片段;

进程 vs 线程:

  • 根本不同: 进程是操作系统 分配的资源,而线程是CPU调度的基本单位;
  • 资源方面: 同一个进程下的线程共享进程中的一些资源,线程同时拥有自身的独立存储空间,进程之间的资源通常是独立的;
  • 数量不同: 进程一般指的是一个进程,而线程是依附于某个进程的,而且一个进程中至少会有一个或多个线程;
  • 开销不同: 毕竟进程和线程不是一个级别的内容,线程的创建和终止的时间是比较短的,而且线程之间的切换比进程之间的切换速度要快很多,而且进程之间的通讯很麻烦,一般要借助内核才可以实现,而线程之间通讯,相当方便;

1.1.2 多线程

多线程是指单个进程中同时运行多个线程;

  • 多线程是为了提高CPU的利用率;
  • 可通过避免一些网络IO或磁盘IO等需要等待的操作,让CPU去调度其他线程;
  • 可大幅度的提升程序的效率,提高用户的体验;

多线程的局限:

  • 如果线程数量特别多,CPU在切换线程上下文时,会额外造成很大的消耗;
  • 任务的拆分需要依赖业务场景,有一些异构化的任务,很难对任务拆分,还有很多业务并不是多线程处理更好;
  • 线程安全问题: 虽然多线程带来了一定的性能提升,但是再做一些操作时,多线程如果操作临界资源,可能会发生一些数据不一致的安全问题,甚至涉及到锁操作时,会造成死锁问题;

1.1.3 串行、并行、并发

概念 核心含义 硬件要求 进程层面举例 线程层面举例
串行 一个接一个执行 单核 CPU 依次运行 A、B、C 三个进程 单线程代码,按顺序调用方法 1、2、3
并发 逻辑同时,微观交替 单核即可 单核 CPU 上,三个进程轮流使用时间片 单核 CPU 上,多线程通过锁/切换交替运行
并行 物理同时 必须多核/多 CPU 4 核 CPU 同时运行 4 个进程 4 核 CPU 上,同一个进程内的 4 个线程分别跑在 4 个核上

总结 :串行、并行、并发描述的是任务的执行方式,既可以指进程,也可以指线程。并发是逻辑上的同时,并行是物理上的同时。

1.1.4 同步异步、阻塞非阻塞

核心概念对比

概念 关注维度 定义 关键词
同步 消息通知机制 调用者主动等待结果返回 等待、主动获取
异步 消息通知机制 被调用者主动通知结果 回调、被动通知
阻塞 线程状态 线程被挂起,无法执行其他任务 挂起、等待
非阻塞 线程状态 线程立即返回,可执行其他任务 立即返回、轮询

四种组合对比

组合模式 调用者状态 结果获取方式 效率 典型应用
同步阻塞 线程挂起等待 主动等待返回 传统BIO、简单应用
同步非阻塞 立即返回(轮询) 主动轮询获取 中(CPU占用高) 极少单独使用
异步阻塞 线程挂起等待 被动通知 极低(逻辑矛盾) 几乎不用
异步非阻塞 立即返回 回调/事件通知 Netty、Node.js、AIO
模式 过程描述 对应技术概念
同步阻塞 打电话点餐后,拿着电话干等40分钟,直到饭做好 线程挂起,等待IO完成
同步非阻塞 挂掉电话,每隔5分钟主动打电话询问饭好了没 轮询检查状态
异步阻塞 餐厅说做好通知你,你拿着电话干等通知 设计不合理,实际少见
异步非阻塞 留下电话,去玩游戏,收到短信通知后取餐 回调机制,事件驱动
特性 同步阻塞 同步非阻塞 异步阻塞 异步非阻塞
线程是否挂起 ✅ 是 ❌ 否 ✅ 是 ❌ 否
是否立即返回 ❌ 否 ✅ 是 ❌ 否 ✅ 是
是否需要轮询 ❌ 否 ✅ 是 ❌ 否 ❌ 否
数据拷贝谁做 用户线程 用户线程 用户线程 内核线程
结果如何获取 主动等待 主动轮询 被动等待 被动回调
编程复杂度
CPU利用率 高(轮询)
吞吐量
适用并发量

IO模型对比

IO模型 同步/异步 阻塞/非阻塞 数据准备阶段 数据拷贝阶段 代表技术
阻塞IO 同步 阻塞 线程阻塞等待 线程参与拷贝 传统BIO
非阻塞IO 同步 非阻塞 轮询检查 线程参与拷贝 极少单独使用
IO多路复用 同步 阻塞(select) 内核通知就绪 线程参与拷贝 epoll、Java NIO
信号驱动IO 同步 非阻塞 信号通知就绪 线程参与拷贝 较少使用
异步IO 异步 非阻塞 内核自动处理 内核完成拷贝 IOCP、io_uring、AIO

数据流程

阶段 同步阻塞 同步非阻塞 IO多路复用 异步非阻塞
发起请求 调用read 调用read 调用select 调用aio_read
数据未就绪 线程阻塞 立即返回错误 select阻塞 立即返回
数据就绪通知 无(直接返回) 轮询发现 select返回 回调/信号
数据拷贝 线程完成 线程完成 线程完成 内核完成
线程参与度 全程参与 轮询+拷贝 等待+拷贝 仅发起请求

误区

误区 错误认知 正确理解
epoll是异步IO epoll是异步的 epoll是同步IO多路复用,数据拷贝仍需用户线程完成
非阻塞就是异步 调用立即返回=异步 非阻塞只表示立即返回,但仍需主动轮询,是同步模式
同步一定效率低 同步性能差 低并发下同步阻塞代码简单易维护,效率并不低
异步一定性能高 异步总是更好 异步编程复杂度高,不适合所有场景
阻塞=同步 阻塞和同步是一回事 阻塞是线程状态,同步是通知机制,两者不同维度

同步 vs 异步

对比维度 同步 异步
通知方式 调用者主动等待 被调用者主动通知
结果获取 调用者自己获取 被调用者推送结果
数据拷贝 用户线程参与 内核自动完成
编程复杂度 简单 复杂
CPU利用率 低(阻塞时)
并发能力 低(受线程数限制)
调试难度 容易 困难
代表场景 简单CRUD应用 高并发网络服务

阻塞 vs 非阻塞

对比维度 阻塞 非阻塞
线程状态 挂起(Waiting/Blocked) 就绪/运行(Runnable/Running)
CPU占用 释放CPU 占用CPU(轮询时)
响应延迟 较低(立即可用) 较高(轮询间隔)
资源消耗 线程栈内存 CPU时间片
并发能力 一个线程一个连接 一个线程多个连接
编程模型 线性顺序执行 状态机/事件驱动

面试高频问答

问题 简洁答案 详细说明
同步和异步的区别? 如何通知 同步主动等待,异步被动通知
阻塞和非阻塞的区别? 线程状态 阻塞挂起,非阻塞立即返回
epoll是同步还是异步? 同步 数据拷贝由用户线程完成
NIO是阻塞还是非阻塞? 非阻塞 但select可以阻塞等待
异步非阻塞的优势? 高并发 线程不等待,数据拷贝不参与
什么时候用同步阻塞? 低并发场景 代码简单,易于维护
什么时候用异步非阻塞? 高并发场景 CPU利用率高,吞吐量大

1.2 线程的创建

1.2.1 继承Thread类 重写run方法

启动线程是调用start方法 ,这样会创建一个新的线程,并执行线程的任务,如果直接调用run方法 ,会让当前线程执行run方法中的业务逻辑;
特点:

  • 简单直接,但Java是单继承,继承了Thread就不能继承其他类;
  • 线程执行的任务与线程对象绑定,耦合度高;
java 复制代码
/**
 * 线程启动顺序不等于执行顺序;
 * 线程执行速度受CPU调度影响;
 * 每次运行结果可能不同(非确定性);
 * 这是多线程并发执行的表现;
 */

public class MyThread01 extends Thread {
    @Override
    public void run() {
//        线程执行的具体任务
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "执行:" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
//        创建并启动线程
        MyThread01 thread01 = new MyThread01();
        MyThread01 thread02 = new MyThread01();
        MyThread01 thread03 = new MyThread01();

        thread01.start();  //启动线程,自动调用run方法
        thread02.start();
        thread03.start();
    }
}

1.2.2 实现Runnable接口 重写run方法

实现Runnable接口,重写run方法,将任务与线程分离;
特点:

  • 避免了单继承的局限,可以继承其他类;
  • 任务与线程解耦,便于复用和测试;
  • 更符合面向对象的设计思想;
java 复制代码
public class MyThread02 implements Runnable {
    private String taskName;

    public MyThread02(String taskName) {
        this.taskName = taskName;
    }

    public MyThread02() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(taskName + "-" + Thread.currentThread().getName() + "执行:" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
//        创建任务对象
        MyThread02 task01 = new MyThread02("任务A");
        MyThread02 task02 = new MyThread02("任务B");
//        MyThread02 task04 = new MyThread02();

//        将任务交给线程执行
        Thread thread01 = new Thread(task01, "线程1");
        Thread thread02 = new Thread(task02, "线程2");
//        Thread thread04 = new Thread(task04);

        thread01.start();
        thread02.start();
//        thread04.start();

//        使用lambda表达式简化
        Thread thread03 = new Thread(() -> {
            System.out.println("使用Lambda创建线程");
        });
        thread03.start();
    }
}

1.2.3 实现Callable 重写call方法,配合FutrueTask

实现Callable接口,重写call方法,支持返回结果和抛出异常;
特点:

  • 可以有返回值;
  • 可以抛出异常;
  • 支持泛型,返回指定类型的结果;
  • 可以通过Future对象获取结果、取消任务、检查状态;
java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class MyThread03 implements Callable<String> {
    private int taskId;

    public MyThread03(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public String call() throws Exception {
        System.out.println("任务" + taskId + "开始执行,线程:" + Thread.currentThread().getName());
        Thread.sleep(1000);  //模拟耗时操作

//        可以抛出异常
        if (taskId == 3) {
            throw new RuntimeException("任务3执行失败");
        }
        return "任务" + taskId + "执行完成,返回结果:" + System.currentTimeMillis();
    }

    public static void main(String[] args) {
//        方式1:使用FutureTask包装Callable任务
        FutureTask<String> futureTask1 = new FutureTask<>(new MyThread03(1));
        Thread thread01 = new Thread(futureTask1);
        thread01.start();
        try {
//            获取返回结果(阻塞知道任务完成)
            String result01 = futureTask1.get();
            System.out.println("获取到结果:" + result01);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

//        方式2:配合线程池使用
        ExecutorService executor = Executors.newFixedThreadPool(3);
        Future<String> future2 = executor.submit(new MyThread03(2));
        try {
//            设置超时时间
            String result2 = future2.get(2, TimeUnit.SECONDS);
            System.out.println("结果:" + result2);
        } catch (TimeoutException e) {
            System.out.println("任务超时");
            future2.cancel(true);  //取消任务
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
//        批量提交多个Callable任务
        ExecutorService executor2 = Executors.newFixedThreadPool(3);
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            futures.add(executor2.submit(new MyThread03(i)));
        }

//        等待所有任务完成
        for (Future<String> future : futures) {
            try {
                System.out.println(future.get());
            } catch (Exception e) {
                System.out.println("任务执行异常:" + e.getMessage());
            }
        }
        executor2.shutdown();
    }
}

1.2.4 基于线程池构建线程

使用线程池管理线程,避免频繁创建销毁线程的开销。
线程池核心参数说明:

  1. corePoolSize: 核心线程数,即使空闲也会保留;
  2. maximumPoolSize: 最大线程数;
  3. keepAliveTime: 空闲线程存活时间;
  4. unit: 时间单位;
  5. workQueue: 任务队列(阻塞队列);
  6. threadFactory: 线程工厂(用于创建线程);
  7. handler: 拒绝策略(队列满且线程数达到最大时的处理策略);

常用的拒绝策略:

  1. AbortPolicy: 直接抛出异常(默认);
  2. CallerRunsPolicy: 在调用者线程中执行任务;
  3. DiscardPolicy: 静默丢弃任务;
  4. DiscardOldestPolicy: 丢弃队列中最旧的任务;
java 复制代码
package com.ruoyi.web.controller.test;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThread04 {

    public static void main(String[] args) {
        // 1. 创建固定大小的线程池
        fixedThreadPoolExample();

        // 2. 创建缓存线程池
        cachedThreadPoolExample();

        // 3. 创建单线程池
        singleThreadExecutorExample();

        // 4. 创建定时任务线程池
        scheduledThreadPoolExample();

        // 5. 自定义线程池(推荐方式)
        customThreadPoolExample();
    }

    // 固定大小的线程池:每次最多有3个线程
    private static void fixedThreadPoolExample() {
        System.out.println("=== 固定大小线程池示例 ===");
        ExecutorService fixedPool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            fixedPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务: " + taskId);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        fixedPool.shutdown(); // 关闭线程池
    }

    // 缓存线程池(根据需要创建新线程,空闲线程会被回收)
    private static void cachedThreadPoolExample() {
        System.out.println("=== 缓存线程池示例 ===");
        ExecutorService cachedPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            cachedPool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务: " + taskId);
                return "完成";
            });
        }
        cachedPool.shutdown();
    }

    // 单线程池(保证任务顺序执行)
    private static void singleThreadExecutorExample() {
        System.out.println("=== 单线程池示例 ===");
        ExecutorService singlePool = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            singlePool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务: " + taskId);
            });
        }

        singlePool.shutdown();
    }

    // 定时任务线程池
    private static void scheduledThreadPoolExample() {
        System.out.println("=== 定时任务线程池示例 ===");
        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);

        // 延迟执行
        scheduledPool.schedule(() -> {
            System.out.println("延迟3秒执行的任务");
        }, 3, TimeUnit.SECONDS);

        // 周期性执行(首次延迟2秒,之后每5秒执行一次)
        scheduledPool.scheduleAtFixedRate(() -> {
            System.out.println("周期性任务: " + System.currentTimeMillis());
        }, 2, 5, TimeUnit.SECONDS);

        // 10秒后关闭线程池(示例用,实际项目中需要合理管理)
        scheduledPool.schedule(() -> scheduledPool.shutdown(), 10, TimeUnit.SECONDS);
    }

    // 自定义线程池(最灵活、最推荐的方式)
    private static void customThreadPoolExample() {
        System.out.println("=== 自定义线程池示例 ===");

        // 创建自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,                    // 核心线程数
                5,                    // 最大线程数
                60,                   // 空闲线程存活时间
                TimeUnit.SECONDS,     // 时间单位
                new LinkedBlockingQueue<>(10),  // 任务队列
                new ThreadFactory() { // 自定义线程工厂
                    private final AtomicInteger threadNumber = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("自定义线程-" + threadNumber.getAndIncrement());
                        thread.setDaemon(false);
                        System.out.println("创建新线程: " + thread.getName());
                        return thread;
                    }
                },
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务: " + taskId);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 监控线程池状态
        new Thread(() -> {
            while (!executor.isTerminated()) {
                System.out.println("线程池状态 - 活跃线程数: " + executor.getActiveCount() +
                        ", 任务队列大小: " + executor.getQueue().size() +
                        ", 已完成任务数: " + executor.getCompletedTaskCount());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }).start();

        // 关闭线程池
        executor.shutdown();
        try {
            // 等待所有任务完成
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow(); // 强制关闭
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }

        System.out.println("所有任务执行完毕");
    }
}

1.3 线程的使用

1.3.1 线程的状态

线程在生命周期中有多种状态,这些状态之间可以相互转换。以下是线程的六种核心状态:

状态 说明
新建 (New) 线程对象创建但未调用 start() 方法
可运行 (Runnable) 调用 start() 后,等待CPU调度执行
运行 (Running) 获得CPU时间片,正在执行 run() 方法
阻塞 (Blocked) 等待获取监视器锁(synchronized)
等待 (Waiting) 无限期等待其他线程唤醒(wait()join()无超时)
超时等待 (Timed Waiting) 有限时间内等待(sleep()wait(timeout)join(timeout)
终止 (Terminated) 线程执行完毕或异常退出

1.3.2 线程的常用方法

方法 作用 使用场景
start() 启动线程,进入就绪状态 线程创建后调用
run() 线程执行体 子类重写此方法
sleep(long millis) 让当前线程休眠指定时间 模拟延时、让出CPU
yield() 让出CPU时间片,进入就绪状态 线程礼让,不释放锁
join() 等待目标线程执行完毕 线程间同步
interrupt() 中断线程 优雅停止线程

优先级设置

复制代码
// 优先级范围 1-10
thread.setPriority(Thread.MAX_PRIORITY);  // 10
thread.setPriority(Thread.NORM_PRIORITY); // 5
thread.setPriority(Thread.MIN_PRIORITY);  // 1

等待与通知(配合 synchronized)

方法 作用
wait() 当前线程进入等待状态,释放锁
wait(long timeout) 超时等待,释放锁
notify() 唤醒一个等待该锁的线程
notifyAll() 唤醒所有等待该锁的线程

注意:

  • wait()、notify()、notifyAll() 必须在 synchronized 代码块中调用;
  • 锁对象必须是调用这些方法的对象;

1.3.3 线程的结束方式

  1. 自然结束(推荐方式)
    线程的run()方法执行完毕,自动终止。
java 复制代码
public class NaturalEnd extends Thread {
    @Override
    public void run() {
        // 执行完业务逻辑后自动结束
        for (int i = 0; i < 10; i++) {
            System.out.println("执行第 " + i + " 次");
        }
        // 方法结束,线程自然终止
    }
}
  1. 标志位退出(推荐方式)
    使用 volatile 标志位控制线程结束。
java 复制代码
public class FlagStop implements Runnable {
    // volatile 保证内存可见性
    private volatile boolean running = true;
    
    @Override
    public void run() {
        while (running) {
            try {
                // 执行业务逻辑
                Thread.sleep(1000);
                System.out.println("线程运行中...");
            } catch (InterruptedException e) {
                // 处理中断异常
                Thread.currentThread().interrupt();
                break;
            }
        }
        System.out.println("线程已停止");
    }
    
    public void stop() {
        this.running = false;
    }
    
    // 使用示例
    public static void main(String[] args) throws InterruptedException {
        FlagStop task = new FlagStop();
        Thread thread = new Thread(task);
        thread.start();
        
        Thread.sleep(5000);
        task.stop();  // 优雅停止
    }
}
  1. 中断机制退出(推荐方式)
    使用 interrupt() 方法配合中断状态检查。
java 复制代码
public class InterruptStop implements Runnable {
    @Override
    public void run() {
        // 检查中断状态
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 执行业务逻辑
                Thread.sleep(1000);  // sleep会自动响应中断
                System.out.println("线程运行中...");
            } catch (InterruptedException e) {
                // 捕获中断异常,重置中断标志位
                Thread.currentThread().interrupt();  // 重新设置中断标志
                System.out.println("检测到中断请求,准备退出");
                break;
            }
        }
        System.out.println("线程已通过中断停止");
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptStop());
        thread.start();
        
        Thread.sleep(5000);
        thread.interrupt();  // 发送中断信号
    }
}
  1. 废弃的 stop() 方法(不推荐)
java 复制代码
// 以下方法已被标记为 @Deprecated,存在安全隐患
thread.stop();        // 可能导致数据不一致
thread.suspend();     // 容易造成死锁
thread.resume();      // 与suspend配合使用

废弃原因:

  • stop(): 直接终止线程,不会释放持有的锁,可能导致数据不一致
  • suspend()/resume(): 挂起线程时不释放锁,容易造成死锁;

结束方式对比

结束方式 推荐度 优点 缺点
自然结束 ⭐⭐⭐⭐⭐ 最简单,安全可靠 需要代码逻辑自然执行完
标志位 ⭐⭐⭐⭐⭐ 可控性强,主动结束 需要正确处理阻塞状态
中断机制 ⭐⭐⭐⭐⭐ Java官方推荐,响应阻塞操作 需要正确处理InterruptedException
stop() ❌ 废弃 不安全,可能破坏数据一致性

二、并发编程的三大特性

并发编程的三大特性是保证线程安全 的核心基础,理解这三大特性有助于编写正确的多线程程序:

2.1 原子性

定义: 原子性是指一个或多个操作在CPU执行过程中不被中断的特性,即这些操作要么全部执行完成,要么全部不执行。

java 复制代码
public class AtomicityExample {
    private int count = 0;
    
    // 非原子操作:count++ 实际上包含三个步骤
    public void increment() {
        count++;  // 1. 读取count值 2. 加1 3. 写回count
    }
    
    // 多线程环境下会出现线程安全问题
}
方式 说明 示例
synchronized 同步锁,保证代码块原子性 synchronized(this) { count++; }
Lock 显式锁,提供更灵活的锁机制 lock.lock(); try { count++; } finally { lock.unlock(); }
AtomicXXX CAS机制实现的原子类 AtomicInteger count = new AtomicInteger(); count.incrementAndGet();
volatile 不能保证原子性 只能保证可见性,不能保证复合操作的原子性
java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // 原子操作
    }
    
    public int getCount() {
        return count.get();
    }
}

2.2 可见性

定义: 可见性是指当一个线程修改了共享变量 的值,其他线程能够立即感知 到这个修改。
问题原因: Java内存模型(JMM)规定,每个线程都有自己的工作内存(本地缓存),线程对变量的操作必须在工作内存中进行,不能直接操作主内存。这导致一个线程修改了变量后,其他线程可能无法立即看到修改。


保证可见性的方式

方式 原理 示例
volatile 强制将修改立即写回主内存,并通知其他线程缓存失效 private volatile boolean flag = true;
synchronized 进入同步块时清空工作内存,退出时刷新到主内存 synchronized(this) { count++; }
Lock 与synchronized类似,保证可见性 lock.lock(); try { count++; } finally { lock.unlock(); }
final 一旦初始化完成,其他线程可见(不发生重排序) private final int value;
java 复制代码
public class VisibilityExample {
    // volatile 保证可见性
    private volatile boolean running = true;
    
    public void stop() {
        running = false;  // 修改立即可见
    }
    
    public void run() {
        while (running) {
            // 能够及时感知到 running 的变化
            System.out.println("运行中...");
        }
    }
}

2.3 有序性

定义: 有序性是指程序执行的顺序按照代码的先后顺序执行。由于指令重排序优化,实际执行顺序可能与代码顺序不一致。
指令重排序: 编译器和处理器为了优化性能,可能会对指令进行重排序,但在单线程环境下保证最终结果一致。

复制代码
// 代码顺序
int a = 1;    // 语句1
int b = 2;    // 语句2
int c = a + b; // 语句3

// 可能的重排序执行顺序
// 语句2 -> 语句1 -> 语句3
// 但不会出现 语句3 在语句1、2之前执行

重排序的三种类型

重排序类型 说明
编译器重排序 编译器在不改变单线程语义的前提下,重新调整执行顺序
处理器重排序 处理器采用乱序执行技术,对指令进行重排序
内存系统重排序 由于缓存和写缓冲区的存在,导致内存操作看起来被重排序

保证有序性的方式

方式 原理 说明
volatile 插入内存屏障,禁止指令重排序 保证volatile变量的读写有序
synchronized 同一时刻只有一个线程执行,天然有序 同步块内的代码不会与其他线程交叉执行
Lock 与synchronized类似 保证临界区的有序性
happens-before规则 JMM定义的一组规则 保证特定操作之间的可见性和有序性
java 复制代码
public class Singleton {
    // volatile 防止指令重排序
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {           // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {   // 第二次检查
                    // 问题:new操作不是原子操作
                    // 1. 分配内存空间
                    // 2. 初始化对象
                    // 3. 将引用指向内存地址
                    // 重排序可能导致 1->3->2
                    // volatile 禁止重排序
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

as-if-serial: 在单线程环境下,重排序后的执行结果必须与按代码顺序执行的结果完全一致。

不论指定如何重排序,需要保证单线程的程序执行结果是不变的。而且如果存在依赖的关系,那么也不可以做指令重排。

java 复制代码
// 这种情况肯定不能做指令重排序
int i = 0;
i++;

// 这种情况肯定不能做指令重排序
int j = 200;
j * 100;
j + 100;
// 这里即便出现了指令重排,也不可以影响最终的结果,20100

happens-before: 是 Java 内存模型(JMM)中定义的一个核心概念,用于保证多线程环境下操作的可见性和有序性。
具体规则:

规则 说明
程序次序规则 同一个线程中,前面的操作happens-before后面的操作
监视器锁规则 unlock操作happens-before后续的lock操作
volatile变量规则 volatile写happens-before后续的volatile读
传递性规则 A happens-before B,B happens-before C,则A happens-before C
线程启动规则 start()方法happens-before该线程的所有操作
线程终止规则 线程的所有操作happens-before其他线程检测到该线程终止
中断规则 调用interrupt()方法happens-before检测到中断事件
对象终结规则 对象构造完成happens-before finalize()方法

volatile: 是Java 虚拟机提供的轻量级同步机制,用于保证共享变量的可见性和有序性,但不能保证原子性。

如果需要让程序对某一个属性的操作不出现指令重排,除了满足happens-before原则之外,还可以基于volatile修饰属性,从而对这个属性的操作,就不会出现指令重排的问题了。
volatile如何实现的禁止指令重排?

  • 内存屏障概念。将内存屏障看成一条指令。
  • 会在两个操作之间,添加上一道指令,这个指令就可以避免上下执行的其他指令进行重排序。

三、锁

3.1 锁的分类

3.1.1 可重入锁、不可重入锁

定义:
可重入锁: 同一个线程可以多次获取同一把锁,不会产生死锁;
不可重入锁: 同一个线程如果已经持有锁,再次尝试获取时会阻塞;

可重入锁(ReentrantLock和synchronized都是可重入锁)

java 复制代码
// 可重入锁示例(ReentrantLock和synchronized都是可重入锁)
public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void outerMethod() {
        lock.lock();  // 第一次获取锁
        try {
            System.out.println("外层方法获取锁");
            innerMethod();  // 同一个线程可以再次获取锁
        } finally {
            lock.unlock();
        }
    }
    
    public void innerMethod() {
        lock.lock();  // 第二次获取锁,可以成功(重入)
        try {
            System.out.println("内层方法再次获取锁");
            // state计数器变为2
        } finally {
            lock.unlock();  // 释放一次,state变为1
        }
    }
}

不可重入锁

java 复制代码
// 模拟不可重入锁的实现
class NonReentrantLock {
    private boolean isLocked = false;
    
    public synchronized void lock() throws InterruptedException {
        // 如果锁已被占用,即使当前线程持有锁也会阻塞
        while (isLocked) {
            wait();
        }
        isLocked = true;
    }
    
    public synchronized void unlock() {
        isLocked = false;
        notify();
    }
}

为什么要设计可重入锁

java 复制代码
public class WhyReentrant {
    // 场景1:递归调用
    public synchronized void recursive(int n) {
        if (n <= 0) return;
        System.out.println("递归调用: " + n);
        recursive(n - 1);  // 如果是不可重入锁,这里会死锁
    }
    
    // 场景2:父子方法调用
    public synchronized void parent() {
        child();  // 同一个锁,可重入避免死锁
    }
    
    public synchronized void child() {
        // 业务逻辑
    }
    
    // 场景3:模板方法模式
    public abstract class BaseClass {
        public synchronized void templateMethod() {
            doSomething();  // 调用子类方法,可能也用了同一把锁
        }
        
        protected abstract void doSomething();
    }
}

3.1.2 乐观锁、悲观锁

乐观锁: 假设不会发生冲突,只在更新时检查是否被修改;
悲观锁: 假设会发生并发冲突,每次访问数据都加锁;

乐观锁

java 复制代码
// 乐观锁示例 - 使用CAS
public class OptimisticLock {
    private AtomicInteger count = new AtomicInteger(0);
    
    // 不加锁,CAS操作
    public void increment() {
        // 乐观地尝试更新,失败则重试
        count.incrementAndGet();  // 内部使用CAS
    }
    
    // 自定义乐观锁实现
    private volatile int value;
    private volatile int version;
    
    public boolean update(int newValue) {
        int currentVersion = version;
        // 模拟业务处理
        if (currentVersion == version) {  // 检查版本
            this.value = newValue;
            this.version++;
            return true;
        }
        return false;  // 版本冲突,需要重试
    }
}

悲观锁

java 复制代码
// 悲观锁示例
public class PessimisticLock {
    private int count = 0;
    private final Object lock = new Object();
    
    // 每次操作都加锁
    public void increment() {
        synchronized (lock) {  // 悲观锁
            count++;
        }
    }
}

应用场景

java 复制代码
// 场景1:数据库乐观锁
@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private Integer stock;
    
    @Version  // JPA版本号,乐观锁
    private Integer version;
    
    // 更新库存时,自动检查版本号
    public void reduceStock(int quantity) {
        // UPDATE product SET stock = stock - ?, version = version + 1 
        // WHERE id = ? AND version = ?
        if (this.stock < quantity) {
            throw new InsufficientStockException();
        }
        this.stock -= quantity;
        // 如果version不匹配,JPA会抛出OptimisticLockException
    }
}

// 场景2:Redis分布式锁(悲观锁)
public class RedisDistributedLock {
    private final RedisTemplate<String, String> redisTemplate;
    
    public boolean tryLock(String key, String value, long timeout) {
        // SET key value NX EX timeout - 悲观锁
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(key, value, Duration.ofSeconds(timeout));
        return Boolean.TRUE.equals(result);
    }
}

// 场景3:选择策略
public class LockChoiceStrategy {
    /**
     * 选择原则:
     * - 写操作多、竞争激烈 → 悲观锁
     * - 读操作多、冲突少 → 乐观锁
     * - 需要保证数据一致性 → 悲观锁
     * - 追求性能、可接受重试 → 乐观锁
     */
    public void chooseLock() {
        // 高并发秒杀场景 - 悲观锁
        seckillWithPessimisticLock();
        
        // 用户信息更新场景 - 乐观锁
        updateUserWithOptimisticLock();
    }
    
    private void seckillWithPessimisticLock() {
        // 秒杀场景,竞争激烈,使用悲观锁
        synchronized (this) {
            // 扣减库存
        }
    }
    
    private void updateUserWithOptimisticLock() {
        // 用户信息更新,冲突少,使用乐观锁
        AtomicReference<User> userRef = new AtomicReference<>();
        User oldUser = userRef.get();
        User newUser = new User(oldUser);
        newUser.setName("new name");
        userRef.compareAndSet(oldUser, newUser);  // 乐观更新
    }
}

3.1.3 公平锁、非公平锁

公平锁: 线程按照请求锁的顺序获取锁(FIFO)。
非公平锁: 线程获取锁的顺序不确定,可能插队。
性能对比

java 复制代码
public class FairnessComparison {
    // 公平锁
    private ReentrantLock fairLock = new ReentrantLock(true);
    // 非公平锁(默认)
    private ReentrantLock unfairLock = new ReentrantLock(false);
    
    public void demonstrate() throws InterruptedException {
        // 测试公平锁
        testLock(fairLock, "公平锁");
        
        // 测试非公平锁
        testLock(unfairLock, "非公平锁");
    }
    
    private void testLock(ReentrantLock lock, String lockType) {
        for (int i = 0; i < 5; i++) {
            int threadId = i;
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(lockType + " - 线程" + threadId + " 获取锁");
                    Thread.sleep(100);  // 模拟工作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }).start();
        }
    }
}

实现原理

java 复制代码
// 公平锁与非公平锁的实现差异(简化版)
class FairnessDemo {
    // 公平锁的获取逻辑
    class FairLock {
        private final Queue<Thread> waitQueue = new ConcurrentLinkedQueue<>();
        
        public void lock() {
            Thread current = Thread.currentThread();
            // 必须排队
            waitQueue.offer(current);
            
            while (waitQueue.peek() != current || !tryAcquire()) {
                // 如果不是队首或获取锁失败,就等待
                LockSupport.park();
            }
            waitQueue.poll();  // 获取锁成功,出队
        }
        
        private boolean tryAcquire() {
            // 尝试获取锁的逻辑
            return true;
        }
    }
    
    // 非公平锁的获取逻辑
    class NonFairLock {
        public void lock() {
            // 先尝试直接获取锁(插队)
            if (tryAcquire()) {
                return;
            }
            // 获取失败才进入等待队列
            // ... 后续排队逻辑
        }
        
        private boolean tryAcquire() {
            // 尝试获取锁的逻辑
            return true;
        }
    }
}

选择建议

java 复制代码
public class FairnessChoice {
    /**
     * 公平锁: 
     * - 优点:避免线程饥饿,所有线程最终都能执行
     * - 缺点:性能较差(约非公平锁的1/10),需要上下文切换
     * - 适用:业务要求严格的执行顺序
     */
    
    /**
     * 非公平锁:
     * - 优点:性能高,吞吐量大
     * - 缺点:可能导致某些线程长期获取不到锁(饥饿)
     * - 适用:大多数业务场景
     */
    
    // 示例:需要严格顺序的场景
    public void needFairLock() {
        ReentrantLock fairLock = new ReentrantLock(true);
        // 银行转账系统,需要按顺序处理交易
        // 避免某些交易长期等待
    }
    
    // 示例:高性能场景
    public void needHighPerformance() {
        ReentrantLock unfairLock = new ReentrantLock(false);
        // 高并发缓存系统,追求吞吐量
    }
}

3.1.4 互斥锁、共享锁

互斥锁: 同一时刻只能有一个线程持有锁(写锁)。
共享锁: 同一时刻可以有多个线程同时持有锁(读锁)。
读写锁实现

java 复制代码
public class ReadWriteLockExample {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();   // 共享锁
    private final Lock writeLock = rwLock.writeLock(); // 互斥锁
    private Map<String, Object> data = new HashMap<>();
    
    // 共享锁:多个线程可以同时读
    public Object read(String key) {
        readLock.lock();  // 共享锁,不互斥
        try {
            return data.get(key);
        } finally {
            readLock.unlock();
        }
    }
    
    // 互斥锁:同一时刻只能一个线程写
    public void write(String key, Object value) {
        writeLock.lock();  // 互斥锁,其他读写线程都会阻塞
        try {
            data.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    
    // 锁升级:不能从共享锁升级为互斥锁
    public void lockUpgrade(String key) {
        readLock.lock();
        try {
            // 读锁状态下不能直接获取写锁,会导致死锁
            // writeLock.lock(); // 错误!会导致死锁
            // 必须先释放读锁
        } finally {
            readLock.unlock();
        }
        
        // 正确的做法:释放读锁后获取写锁
        writeLock.lock();
        try {
            // 写操作
        } finally {
            writeLock.unlock();
        }
    }
    
    // 锁降级:可以从互斥锁降级为共享锁
    public void lockDowngrade(String key, Object value) {
        writeLock.lock();
        try {
            data.put(key, value);
            // 在释放写锁前获取读锁,实现锁降级
            readLock.lock();
        } finally {
            writeLock.unlock();  // 释放写锁,仍持有读锁
        }
        
        try {
            // 此时仍然是读锁状态,可以读数据
            System.out.println("Lock downgrade: " + data.get(key));
        } finally {
            readLock.unlock();
        }
    }
}

互斥锁和共享锁状态实现

java 复制代码
// 读写锁的状态表示(AQS实现)
class ReadWriteLockState {
    /**
     * 用一个int变量state表示锁状态:
     * 高16位:读锁计数(共享锁)
     * 低16位:写锁计数(互斥锁)
     */
    private volatile int state;
    
    // 获取读锁计数(共享锁数量)
    int getReadLockCount() {
        return state >>> 16;  // 无符号右移16位
    }
    
    // 获取写锁计数(互斥锁数量,只能是0或1,但支持重入)
    int getWriteLockCount() {
        return state & 0xFFFF;  // 低16位
    }
    
    // 尝试获取读锁(共享锁)
    boolean tryAcquireShared() {
        int c = state;
        int writeCount = c & 0xFFFF;
        
        // 如果有写锁且不是当前线程持有,获取失败
        if (writeCount != 0 && 
            Thread.currentThread() != getExclusiveOwnerThread()) {
            return false;
        }
        
        // 增加读锁计数
        int readCount = (c >>> 16) + 1;
        if (compareAndSetState(c, (readCount << 16) | writeCount)) {
            return true;
        }
        return false;
    }
    
    // 尝试获取写锁(互斥锁)
    boolean tryAcquireExclusive() {
        int c = state;
        int writeCount = c & 0xFFFF;
        int readCount = c >>> 16;
        
        // 如果有读锁或者其他线程持有写锁,获取失败
        if (readCount > 0) return false;
        if (writeCount > 0 && 
            Thread.currentThread() != getExclusiveOwnerThread()) {
            return false;
        }
        
        // 增加写锁计数
        if (compareAndSetState(c, c + 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

实际应用场景

java 复制代码
// 场景1:缓存系统(读多写少)
public class Cache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
    // 读操作使用共享锁,高并发读
    public V get(K key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // 写操作使用互斥锁,保证数据一致性
    public void put(K key, V value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// 场景2:倒排索引(读写分离)
public class InvertedIndex {
    private final Map<String, Set<Integer>> index = new HashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
    // 搜索操作:多个线程同时搜索
    public Set<Integer> search(String keyword) {
        lock.readLock().lock();
        try {
            return new HashSet<>(index.getOrDefault(keyword, Collections.emptySet()));
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // 索引更新:互斥更新
    public void addDocument(int docId, String[] keywords) {
        lock.writeLock().lock();
        try {
            for (String keyword : keywords) {
                index.computeIfAbsent(keyword, k -> new HashSet<>()).add(docId);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }
}
锁类型 核心特点 优点 缺点 适用场景
可重入锁 同一线程可重复获取 避免死锁,方便递归 需要正确释放(计数) 递归调用、父子方法
不可重入锁 不能重复获取 实现简单 容易死锁 不推荐使用
乐观锁 不加锁,更新时检查 性能高,无阻塞 冲突时重试开销 读多写少,冲突少
悲观锁 每次操作都加锁 数据安全,无重试 性能低,可能死锁 写操作多,竞争激烈
公平锁 FIFO顺序获取 无线程饥饿 性能差 需要严格顺序
非公平锁 可能插队 性能好,吞吐高 可能饥饿 大多数场景
互斥锁 独占锁 保证写操作安全 读操作也互斥 写操作
共享锁 多个线程可共享 提高读并发 写操作需等待 读操作

3.2 深入synchronized

3.2.1 类锁、对象锁

类锁: 锁的是Class对象,全局唯一,所有实例共享一把锁;

java 复制代码
public class ClassLockDemo {
    // 锁住Class对象
    public static synchronized void staticMethod() {
        System.out.println("类锁 - 静态方法");
    }
    
    public void method() {
        // 锁住Class对象
        synchronized(ClassLockDemo.class) {
            System.out.println("类锁 - 代码块");
        }
    }
}

对象锁: 锁的是实例对象,不同实例之间的锁互不影响;

java 复制代码
public class ObjectLockDemo {
    // 锁住当前实例对象
    public synchronized void method1() {
        System.out.println("对象锁 - 实例方法");
    }
    
    // 锁住指定的对象
    private final Object lock = new Object();
    public void method2() {
        synchronized(lock) {
            System.out.println("对象锁 - 代码块");
        }
    }
}

3.2.2 synchronized的优化

  • 锁粗化: 将多次连续的加锁解锁操作合并(扩大锁的范围,减少锁的获取/释放次数)
  • 锁消除: JIT编译器检测到不可能存在竞争的锁,直接消除
  • 适应性自旋: 根据历史经验调整自旋次数
java 复制代码
// 锁粗化示例 - 优化前
for(int i = 0; i < 100; i++) {
    synchronized(this) {
        // 操作
    }
}

// 锁粗化 - 优化后
synchronized(this) {
    for(int i = 0; i < 100; i++) {
        // 操作
    }
}

3.2.3 synchronized实现原理

字节码层面

class 复制代码
public class SyncPrinciple {
    public void method() {
        synchronized(this) {
            System.out.println("sync block");
        }
    }
}

编译后的字节码

monitorenter 复制代码
// 业务代码
monitorexit   // 退出同步块
monitorexit   // 异常退出路径

JVM层面

  • monitorenter: 尝试获取对象监视器(Monitor)
  • monitorexit: 释放对象监视器
  • 每个对象都有一个Monitor与之关联

3.2.4 synchronized的锁升级

synchronized在JDK 1.6后引入了锁升级机制,从低到高依次是:

复制代码
无锁 → 偏向锁 → 轻量级锁 → 重量级锁

锁升级过程

java 复制代码
public class LockUpgradeDemo {
    private static Object lock = new Object();
    
    public static void main(String[] args) {
        // 1. 无锁状态:初始状态
        
        // 2. 偏向锁:同一个线程重复获取锁
        for(int i = 0; i < 100; i++) {
            synchronized(lock) {
                // 同一个线程,使用偏向锁
            }
        }
        
        // 3. 轻量级锁:不同线程交替执行
        new Thread(() -> {
            synchronized(lock) {
                // 线程A获取锁,升级为轻量级锁
            }
        }).start();
        
        new Thread(() -> {
            synchronized(lock) {
                // 线程B获取锁时发现已被持有,继续CAS自旋
            }
        }).start();
        
        // 4. 重量级锁:竞争激烈,自旋失败
        for(int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized(lock) {
                    // 多个线程同时竞争,升级为重量级锁
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

锁升级条件

锁状态 触发条件 特点 适用场景
无锁 初始状态 无锁竞争 对象刚创建
偏向锁 同一个线程重复获取锁 记录线程ID,无需CAS操作,性能最好 单线程访问
轻量级锁 不同线程交替执行,CAS自旋成功 通过CAS自旋获取锁,无阻塞,用户态操作 多线程交替,竞争不激烈
重量级锁 多线程同时竞争,CAS自旋失败超过阈值 阻塞等待,操作系统调度,涉及用户态到内核态切换 多线程激烈竞争,锁持有时间长

3.2.5 重量锁底层ObjectMonitor

ObjectMonitor结构

text 复制代码
// HotSpot源码中的ObjectMonitor结构
ObjectMonitor() {
    _header       = NULL;      // 对象头
    _count        = 0;         // 计数器
    _waiters      = 0;         // 等待线程数
    _recursions   = 0;         // 重入次数
    _object       = NULL;      // 关联的对象
    _owner        = NULL;      // 当前持有锁的线程
    _WaitSet      = NULL;      // 等待队列(调用wait的线程)
    _EntryList    = NULL;      // 入口队列(等待获取锁的线程)
}

ObjectMonitor工作机制

java 复制代码
public class MonitorDemo {
    private final Object lock = new Object();
    
    public void monitorMechanism() {
        synchronized(lock) {  // 1. 尝试获取Monitor
            try {
                lock.wait();   // 2. 线程进入_WaitSet
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.notify();     // 3. 唤醒_WaitSet中的线程
        }  // 4. 释放Monitor
    }
}

核心机制详解

  1. 锁竞争流程

    线程尝试获取锁

    CAS尝试获取Monitor

    成功? → 是 → 设置_owner为当前线程
    ↓否
    进入_EntryList排队

    通过操作系统mutex阻塞

    被唤醒后继续竞争

  2. 等待/通知机制

java 复制代码
// wait()操作
void ObjectMonitor::wait() {
    // 1. 将当前线程加入_WaitSet
    // 2. 释放锁(_owner = NULL)
    // 3. 线程阻塞等待
}

// notify()操作
void ObjectMonitor::notify() {
    // 1. 从_WaitSet中取出一个线程
    // 2. 移动到_EntryList
    // 3. 等待获取锁
}
  1. 重入锁实现
java 复制代码
// 重入锁实现原理
if (_owner == current_thread) {
    _recursions++;  // 重入计数器+1
    return;         // 直接进入,无需再次竞争
}

3.3 深入ReentrantLock

3.3.1 ReentrantLock和synchronized的区别

两者都是 Java 中实现锁机制的可重入锁,但存在本质区别:

维度 synchronized ReentrantLock
底层实现 JVM 级别的关键字,通过监视器(Monitor)实现 JDK 级别的 API,通过 AQS 实现
锁获取方式 隐式获取,由 JVM 自动释放 显式获取 (lock()),必须显式释放 (unlock())
灵活性 低,不支持中断、超时 高,支持 tryLocklockInterruptibly、超时等待
公平性 非公平锁 默认非公平,构造函数支持公平锁
条件变量 只有单一的 wait/notify 队列 支持多个 Condition 对象,实现更细粒度的等待/唤醒
性能 早期性能较差,经过优化后(偏向锁、轻量级锁)差距缩小 在高竞争场景下仍保持较高吞吐量

3.3.2 AQS概述

AbstractQueuedSynchronizer (AQS) 是 JUC 包中锁和同步器的基石。
核心思想

  • 状态管理: 通过volatile int state 表示同步状态(如锁的重入次数);
  • 队列管理: 内置CLH锁队列(FIFO双向链表) ,管理获取同步状态失败的线程;
  • 资源共享方式:
    独占模式: 只有一个线程能持有资源(如 ReentrantLock);
    共享模式: 多个线程可同时持有资源(如 CountDownLatch、Semaphore);

关键数据结构

java 复制代码
static final class Node {
    volatile int waitStatus;  // 等待状态:CANCELLED(1), SIGNAL(-1), CONDITION(-2), PROPAGATE(-3)
    volatile Node prev;       // 前驱节点
    volatile Node next;       // 后继节点
    volatile Thread thread;   // 当前节点对应的线程
    Node nextWaiter;          // 用于 Condition 队列的链接
}

3.3.3 加锁流程源码剖析

非公平锁为例,剖析 lock() 方法的核心流程。

  1. 入口:NonfairSync.lock()
java 复制代码
final void lock() {
    // 上来先尝试 CAS 抢一次锁(非公平性的体现)
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);  // 抢失败则进入 AQS 流程
}
  1. acquire(int arg) 模板方法
java 复制代码
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&          // 再次尝试获取锁(可能处理重入)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并阻塞
        selfInterrupt();
}
  • tryAcquire:非公平实现
java 复制代码
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 重点:这里依然会 CAS 抢锁,可能被新来的线程插队
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) throw new Error("Maximum lock count exceeded");
        setState(nextc);  // 重入计数
        return true;
    }
    return false;
}
  • addWaiter:将当前线程包装为 Node 加入队列
    快速尝试入队尾部,失败则进入 enq 自旋入队。
    enq 中会初始化空头节点(哨兵节点)。
  • acquireQueued:节点在队列中阻塞
java 复制代码
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 如果前驱是头节点,则再尝试一次获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // 帮助 GC
                failed = false;
                return interrupted;
            }
            // 检查是否需要阻塞(park)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire:维护前驱节点的 waitStatus 为 SIGNAL,表示需要唤醒后继节点。

parkAndCheckInterrupt:调用 LockSupport.park() 阻塞线程。

3.3.4 释放锁流程源码剖析

  1. unlock() → release(int arg)
java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // 头节点不为空且 waitStatus 不为 0(说明有后继节点需要唤醒)
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  1. tryRelease:ReentrantLock 实现
java 复制代码
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

只有当 state 减到 0 时,才真正释放锁(完全释放)。

  1. unparkSuccessor:唤醒后继节点
  • 找到头节点后第一个有效节点(waitStatus <= 0),通过 LockSupport.unpark() 唤醒该线程。
  • 被唤醒的线程会从 parkAndCheckInterrupt() 返回,继续在 acquireQueued 中自旋尝试获取锁。

3.3.5 AQS中常见的问题

Q1:AQS 的队列为什么是双向链表?

单向性:从 head 向后遍历,用于唤醒后继节点。

逆向性:在节点取消(cancelAcquire)时,需要从后向前遍历跳过已取消的节点,因此需要 prev 指针。
Q2:公平锁与非公平锁的区别在哪里?

公平锁:tryAcquire 中增加 hasQueuedPredecessors() 判断,如果队列中有等待线程,则不会抢锁。

非公平锁:在 lock() 和 tryAcquire 中多次尝试 CAS 抢占,可能导致线程饥饿。
Q3:waitStatus 中的 SIGNAL 作用是什么?

表示当前节点的后继节点被阻塞(或被即将阻塞),当前节点释放锁或取消时,必须唤醒后继节点。

这是确保线程安全唤醒的关键状态。
Q4:AQS 如何避免虚假唤醒?

AQS 使用 for(;😉 循环自旋,每次被唤醒后重新检查前驱是否为头节点,并再次尝试获取锁,符合 循环检查条件 的范式。

3.3.6 ConditionObject

ConditionObject 是 AQS 的内部类,实现了 Condition 接口,用于实现等待/通知 机制。
核心结构

java 复制代码
public class ConditionObject implements Condition {
    private Node firstWaiter;  // 条件队列的头节点
    private Node lastWaiter;   // 条件队列的尾节点
}
  1. await() 流程
  • 添加到条件队列: 将当前线程封装为 Node.CONDITION 节点,插入条件队列。
  • 释放锁: 调用 fullyRelease,释放当前线程持有的锁(并唤醒同步队列中的后继节点)。
  • 阻塞等待: 进入 while (!isOnSyncQueue(node)) 循环,调用 LockSupport.park 阻塞。
  • 重新获取锁: 被 signal 唤醒后,节点被转移到同步队列,通过 acquireQueued 重新竞争锁。
  1. signal() 流程
java 复制代码
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
  • doSignal:将条件队列的头节点转移到同步队列(通过 enq)。
  • 转移前会清除 CONDITION 状态,并设置前驱节点的 waitStatus 为 SIGNAL。

3.4 深入ReentrantReadWriteLock

3.4.1 为什么要出现读写锁

问题背景

传统的互斥锁(如ReentrantLock)虽然保证了线程安全,但存在性能问题:

  • 读操作之间完全互斥,即使只是读取数据也要排队
  • 读多写少的场景下,并发性能很差
java 复制代码
// 传统互斥锁的问题
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 多个线程同时读操作也要串行执行
    readData(); 
} finally {
    lock.unlock();
}

// 读写锁的优势
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读操作可以并发执行
rwLock.readLock().lock();
try {
    readData(); // 多个线程可以同时执行
} finally {
    rwLock.readLock().unlock();
}

核心原则

  • 读-读:不互斥,可以并发
  • 读-写:互斥,不能并发
  • 写-写:互斥,不能并发

3.4.2 读写锁的实现原理

状态位设计

ReentrantReadWriteLock巧妙地将一个int类型的state拆分成两部分:

java 复制代码
// 在AbstractQueuedSynchronizer中
// state的高16位表示读锁的持有次数
// state的低16位表示写锁的持有次数
// 例如:state = 0x00010002
// 高16位:0x0001 = 1 (读锁计数)
// 低16位:0x0002 = 2 (写锁重入次数)

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT); // 65536
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535

// 获取读锁计数
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
// 获取写锁计数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

锁降级

java 复制代码
class DataContainer {
    private volatile Object data;
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    public void processData() {
        // 先获取读锁
        rwLock.readLock().lock();
        if (data == null) {
            // 读锁释放前获取写锁(锁降级)
            rwLock.readLock().unlock();
            rwLock.writeLock().lock();
            try {
                if (data == null) {
                    data = loadData(); // 双重检查
                }
                // 写锁释放前获取读锁
                rwLock.readLock().lock();
            } finally {
                rwLock.writeLock().unlock();
            }
        }
        try {
            useData(data); // 使用数据
        } finally {
            rwLock.readLock().unlock();
        }
    }
}

3.4.3 写锁分析

写锁的分析

java 复制代码
// 写锁的tryAcquire实现
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    
    if (c != 0) {
        // 存在锁
        if (w == 0 || current != getExclusiveOwnerThread()) {
            // 存在读锁 或者 当前线程不是持有写锁的线程
            return false;
        }
        // 写锁重入
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        setState(c + acquires);
        return true;
    }
    
    // 无锁状态,尝试获取写锁
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

写锁的特点

  • 独占锁: 同一时刻只有一个线程能获得
  • 可重入: 支持重入,重入次数记录在低16位
  • 支持条件变量: 可以创建Condition对象

3.4.4 读锁分析

读锁的获取流程

java 复制代码
// 读锁的tryAcquireShared实现
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    
    // 如果有写锁且持有者不是当前线程,获取失败
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    
    int r = sharedCount(c);
    // 尝试获取读锁
    if (!readerShouldBlock() && r < MAX_COUNT && 
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 记录读锁持有信息
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            // 使用ThreadLocal记录重入次数
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            rh.count++;
        }
        return 1;
    }
    // 完整获取逻辑
    return fullTryAcquireShared(current);
}

读锁的特点

  • 共享锁: 多个线程可以同时持有
  • 可重入: 支持重入,重入次数记录在高16位
  • 不支持条件变量: 读锁不能创建Condition

3.4.5 死锁问题

常见的死锁场景

  • 场景1:锁升级导致死锁
java 复制代码
public class LockUpgradeDeadlock {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    public void deadlockScenario() {
        // 线程A持有读锁
        rwLock.readLock().lock();
        try {
            // 尝试获取写锁 - 死锁!
            // 读锁未释放,写锁永远获取不到
            rwLock.writeLock().lock();
            try {
                updateData();
            } finally {
                rwLock.writeLock().unlock();
            }
        } finally {
            rwLock.readLock().unlock();
        }
    }
}

解决方案

java 复制代码
// 正确的锁降级,不要尝试锁升级
public void correctWay() {
    rwLock.writeLock().lock();
    try {
        updateData();
        // 写锁降级为读锁
        rwLock.readLock().lock();
    } finally {
        rwLock.writeLock().unlock();
    }
    try {
        readData();
    } finally {
        rwLock.readLock().unlock();
    }
}
  • 场景2:循环依赖死锁
java 复制代码
public class CircularDependencyDeadlock {
    private final ReentrantReadWriteLock lock1 = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock();
    
    public void method1() {
        lock1.writeLock().lock();
        try {
            Thread.sleep(100);
            lock2.readLock().lock(); // 等待lock2
            try {
                doSomething();
            } finally {
                lock2.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.writeLock().unlock();
        }
    }
    
    public void method2() {
        lock2.writeLock().lock();
        try {
            Thread.sleep(100);
            lock1.readLock().lock(); // 等待lock1,形成死锁
            try {
                doSomething();
            } finally {
                lock1.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock2.writeLock().unlock();
        }
    }
}

解决方案

java 复制代码
public class CircularDependencyDeadlock {
    private final ReentrantReadWriteLock lock1 = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock();
    
    public void method1() {
        lock1.writeLock().lock();
        try {
            Thread.sleep(100);
            lock2.readLock().lock(); // 等待lock2
            try {
                doSomething();
            } finally {
                lock2.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.writeLock().unlock();
        }
    }
    
    public void method2() {
        lock2.writeLock().lock();
        try {
            Thread.sleep(100);
            lock1.readLock().lock(); // 等待lock1,形成死锁
            try {
                doSomething();
            } finally {
                lock1.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock2.writeLock().unlock();
        }
    }
}
  • 场景3:线程饥饿问题
java 复制代码
public class WriteStarvation {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    // 读线程过多可能导致写线程饥饿
    public void readHeavy() {
        // 大量读线程
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                while (true) {
                    rwLock.readLock().lock();
                    try {
                        Thread.sleep(10); // 读操作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        rwLock.readLock().unlock();
                    }
                }
            }).start();
        }
        
        // 写线程可能永远获取不到锁
        new Thread(() -> {
            while (true) {
                rwLock.writeLock().lock();
                try {
                    System.out.println("写操作执行");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    rwLock.writeLock().unlock();
                }
            }
        }).start();
    }
}
  • 避免死锁的最佳实践

  • 避免锁升级: 不要在读锁未释放时获取写锁

  • 统一锁顺序: 多个锁时,所有线程以相同的顺序获取锁

  • 使用tryLock:

java 复制代码
public boolean tryLockBoth(ReentrantReadWriteLock lock1, ReentrantReadWriteLock lock2) {
    if (lock1.writeLock().tryLock()) {
        try {
            if (lock2.writeLock().tryLock()) {
                try {
                    // 成功获取两个锁
                    return true;
                } finally {
                    lock2.writeLock().unlock();
                }
            }
        } finally {
            lock1.writeLock().unlock();
        }
    }
    return false;
}
  • 考虑使用StampedLock: Java 8引入的StampedLock提供了乐观读锁,可以避免某些死锁场景

  • 设置超时时间:

java 复制代码
public boolean tryLockWithTimeout(ReentrantReadWriteLock lock) {
    try {
        return lock.writeLock().tryLock(1, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        return false;
    }
}

四、阻塞队列

4.1 基本概念

阻塞队列是支持两个附加操作的队列:

操作 普通队列 阻塞队列
插入 满时抛异常/返回false 满时阻塞等待
移除 空时抛异常/返回null 空时阻塞等待
核心方法分类
java 复制代码
// 抛出异常
add(e) / remove() / element()

// 返回特殊值(不阻塞)
offer(e) / poll() / peek()

// 阻塞等待(核心特性)
put(e)  / take()  // 无限等待
offer(e, time, unit) / poll(time, unit)  // 超时等待

阻塞队列的核心价值

复制代码
生产者-消费者模式的完美实现:
┌─────────┐     ┌─────────────┐     ┌─────────┐
│ Producer│────→│ BlockingQueue │────→│ Consumer│
│  (生产)  │     │   (缓冲池)    │     │  (消费)  │
└─────────┘     └─────────────┘     └─────────┘
         队列满时生产者阻塞    队列空时消费者阻塞

4.2 ArrayBlockingQueue

核心特性

特性 说明
底层结构 循环数组(Object[]
有界队列 必须指定容量,固定大小
锁机制 单锁ReentrantLock
公平性 可选择公平/非公平锁
内存 预先分配,内存紧凑

源码结构

java 复制代码
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
                               implements BlockingQueue<E>, Serializable {
    
    // 核心成员
    final Object[] items;      // 循环数组存储元素
    int takeIndex;             // 取元素索引
    int putIndex;              // 放元素索引
    int count;                 // 当前元素数量
    
    final ReentrantLock lock;  // 单锁控制所有操作
    private final Condition notEmpty;  // 等待队列非空
    private final Condition notFull;   // 等待队列非满
}

工作原理图解

复制代码
初始状态(capacity=5):
[_, _, _, _, _]
 t↑          p↑
 
放入3个元素后:
[A, B, C, _, _]
 t↑      p↑
 
取出1个元素后(循环特性):
[_, B, C, _, _]
     t↑  p↑
     
继续放满后:
[_, B, C, D, E]
     t↑      p↑
     
再取2个(循环到头部):
[_, _, _, D, E]
 t↑          p↑

关键代码解析

java 复制代码
// 插入元素(阻塞)
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();  // 可中断获取锁
    try {
        while (count == items.length)  // 队列满时等待
            notFull.await();
        enqueue(e);              // 入队
        notEmpty.signal();       // 唤醒消费者
    } finally {
        lock.unlock();
    }
}

// 取出元素(阻塞)
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)       // 队列空时等待
            notEmpty.await();
        return dequeue();        // 出队
    } finally {
        lock.unlock();
    }
}

使用场景

java 复制代码
// 创建:公平锁模式(按等待时间顺序获取锁)
ArrayBlockingQueue<Task> queue = new ArrayBlockingQueue<>(1000, true);

// 典型场景:固定大小的线程池任务队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4, 8, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)  // 有界队列防止OOM
);

4.3 LinkedBlockingQueue

核心特征

特性 说明
底层结构 单向链表(Node<E>
可选有界/无界 默认Integer.MAX_VALUE(无界)
锁机制 双锁分离putLock + takeLock
并发性 读写可并行,吞吐量更高
内存 动态分配,节点有额外开销

源码结构

java 复制代码
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
                                implements BlockingQueue<E>, Serializable {
    
    // 链表节点
    static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }
    
    // 双锁分离设计!
    private final ReentrantLock takeLock = new ReentrantLock();
    private final Condition notEmpty = takeLock.newCondition();
    
    private final ReentrantLock putLock = new ReentrantLock();
    private final Condition notFull = putLock.newCondition();
    
    private final int capacity;  // 容量,默认Integer.MAX_VALUE
    private final AtomicInteger count = new AtomicInteger(); // 原子计数
}

双锁分离图解

复制代码
传统单锁(ArrayBlockingQueue):
生产者 ──→ [  锁  ] ←── 消费者
           读写互斥

双锁分离(LinkedBlockingQueue):
生产者 ──→ [putLock] 
                    \
                     [队列]  ←── 读写可并行!
                    /
消费者 ──→ [takeLock]

关键代码图解

java 复制代码
// 插入元素 - 只加putLock
public void put(E e) throws InterruptedException {
    if (count.get() == capacity) {  // 先检查,避免加锁
        final ReentrantLock putLock = this.putLock;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity)
                notFull.await();
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();  // 原子操作
            if (c + 1 < capacity)
                notFull.signal();  // 唤醒其他生产者
        } finally {
            putLock.unlock();
        }
    }
    // 插入成功后,可能需要唤醒消费者
    if (c == 0)
        signalNotEmpty();  // 加takeLock唤醒
}

// 取出元素 - 只加takeLock
public E take() throws InterruptedException {
    // 对称实现,加takeLock操作
}

Array vs Linked 对比

对比项 ArrayBlockingQueue LinkedBlockingQueue
内存 预分配,紧凑 动态分配,节点开销大
锁竞争 单锁,竞争大 双锁,读写并行
吞吐量 较低 更高(约2倍)
GC影响 小(无新对象) 大(频繁创建Node)
公平性 支持 不支持
使用建议 固定容量、内存敏感 高并发、需要动态扩容

4.4 PriorityBlockingQueue概念

核心特性

特性 说明
底层结构 堆(完全二叉树,数组实现)
排序方式 自然顺序或Comparator
无界队列 动态扩容(64→2倍增长)
单锁 ReentrantLock
相等处理 不保证FIFO(堆不稳定)
堆结构图解
复制代码
逻辑结构(最大堆):
       100
      /   \
    80     90
   /  \   /
  70  60 50

物理存储(数组):
[100, 80, 90, 70, 60, 50, ...]
  0    1   2   3   4   5

核心操作复杂度

操作 时间复杂度 说明
put/offer O(log n) 插入后上浮(siftUp)
take/poll O(log n) 移除后下沉(siftDown)
peek O(1) 直接取数组[0]
使用场景
java 复制代码
// 任务优先级队列
PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>(
    11,  // 初始容量
    Comparator.comparing(Task::getPriority).reversed()  // 高优先级先执行
);

// 自定义元素必须实现Comparable
class Task implements Comparable<Task> {
    int priority;
    String name;
    
    @Override
    public int compareTo(Task o) {
        return Integer.compare(this.priority, o.priority);
    }
}

注意事项

java 复制代码
// ⚠️ 重要:迭代器不保证顺序!
for (Task t : queue) {  // 无序遍历
    System.out.println(t); 
}

// 正确获取有序元素
while (!queue.isEmpty()) {
    Task t = queue.take();  // 按优先级取出
}

4.5 DelayQueue

核心特性

特性 说明
元素要求 必须实现Delayed接口
排序依据 按剩余延迟时间排序
获取特性 只有到期的元素才能被取出
Leader-Follower 优化多线程等待效率
应用 定时任务、缓存过期、重试机制
Delayed接口
java 复制代码
public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);  // 剩余延迟时间
    // 继承Comparable用于队列内排序
}

工作原理

复制代码
队列内部状态:
[任务A(还剩5s), 任务B(还剩10s), 任务C(还剩30s)]
       ↑
    take()只能获取到期的元素

线程获取流程:
1. 检查队首元素是否到期(delay <= 0)
2. 未到期:Leader线程等待剩余时间,其他线程无限等待
3. 到期后:取出元素,唤醒Follower竞争新Leader

经典实现:缓存过期

java 复制代码
public class DelayedCache<K, V> {
    private final Map<K, V> cache = new ConcurrentHashMap<>();
    private final DelayQueue<DelayedItem<K>> delayQueue = new DelayQueue<>();
    
    // 带过期时间的缓存项
    private static class DelayedItem<K> implements Delayed {
        private final K key;
        private final long expireTime;  // 到期时间戳
        
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
        }
        
        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expireTime, ((DelayedItem<?>)o).expireTime);
        }
    }
    
    public void put(K key, V value, long timeout, TimeUnit unit) {
        cache.put(key, value);
        DelayedItem<K> item = new DelayedItem<>(key, unit.toNanos(timeout));
        delayQueue.offer(item);
    }
    
    // 后台清理线程
    public void startCleanupThread() {
        Thread t = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    DelayedItem<K> item = delayQueue.take();  // 阻塞等待到期
                    cache.remove(item.key);  // 自动清理
                    System.out.println("Expired: " + item.key);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        t.setDaemon(true);
        t.start();
    }
}

使用场景

场景 实现方式
定时任务调度 ScheduledThreadPoolExecutor内部使用
订单超时取消 订单创建时放入DelayQueue,到期自动取消
RPC重试退避 失败请求延迟后放入,实现指数退避
连接池空闲检测 空闲连接延迟回收

4.6 SynchronousQueue

核心特性

特性 说明
容量 零容量! 不存储任何元素
模式 直接传递(handoff)
公平性 公平/非公平模式可选
实现 栈(非公平)或队列(公平)
吞吐量 极高,无缓存开销
核心原理
复制代码
SynchronousQueue = 匹配机制,不是真正的"队列"

生产者put() ──┐
              ├──→ 直接配对成功 → 同时完成
消费者take() ─┘
              │
              └──→ 未配对成功 → 阻塞等待

比喻:像面对面交易,没有柜台(缓冲区)

两种实现方式

java 复制代码
// 1. 非公平模式(默认)- 栈实现(LIFO)
SynchronousQueue<Integer> stackQueue = new SynchronousQueue<>(false);
// 特点:后到的线程先匹配,吞吐量高,可能饥饿

// 2. 公平模式 - 队列实现(FIFO)  
SynchronousQueue<Integer> fairQueue = new SynchronousQueue<>(true);
// 特点:先到先服务,避免饥饿,吞吐量略低

五、高频面试题

类别 问题 核心答案 关键考点
线程基础 进程与线程的区别? 进程是资源分配单位,线程是CPU调度单位;线程共享进程资源,切换开销更小 资源分配 vs 调度单位
多线程的局限? 上下文切换开销、任务拆分难度、线程安全问题(数据不一致、死锁) 开销与风险权衡
并发 vs 并行? 并发是逻辑同时(单核交替),并行是物理同时(多核同时) 逻辑同时 vs 物理同时
创建线程的4种方式? ①继承Thread ②实现Runnable ③实现Callable ④线程池(推荐) 线程池是生产环境最佳实践
start() vs run()? start()启动新线程异步执行;run()是普通方法调用,同步执行 是否创建新线程
线程的6种状态? New→Runnable→Running→Blocked/Waiting/Timed Waiting→Terminated 状态转换条件
线程结束的3种推荐方式? ①自然结束 ②volatile标志位 ③interrupt()中断机制 stop()/suspend()已废弃
同步/异步 同步 vs 异步? 同步:调用者主动等待;异步:被调用者回调通知 结果通知机制
阻塞 vs 非阻塞? 阻塞:线程挂起释放CPU;非阻塞:立即返回,可能轮询 线程状态差异
四种组合模式? 同步阻塞(BIO)、同步非阻塞(轮询)、异步阻塞(矛盾)、异步非阻塞(Netty/AIO) 异步非阻塞效率最高
epoll是异步IO吗? ,epoll是同步IO多路复用,数据拷贝仍需用户线程 常见误区
并发三大特性 什么是原子性?如何保证? 操作不可中断;synchronized、Lock、AtomicXXX(CAS) volatile不能保证原子性
什么是可见性?如何保证? 修改对其他线程立即可见;volatile、synchronized、Lock、final JMM工作内存模型
什么是有序性?如何保证? 禁止指令重排序;volatile(内存屏障)、synchronized、happens-before as-if-serial与happens-before规则
volatile的作用与局限? ✅可见性+有序性;❌不能保证原子性(如i++) 双重检查锁单例模式
锁机制 synchronized vs ReentrantLock? synchronized:JVM关键字,自动释放;ReentrantLock:API级,支持中断/超时/多Condition 灵活性与功能对比
synchronized锁升级过程? 无锁→偏向锁(同线程)→轻量级锁(CAS自旋)→重量级锁(内核阻塞) 优化路径与触发条件
什么是可重入锁? 同线程可多次获取同一把锁,计数器记录重入次数;避免递归/父子方法死锁 ReentrantLock/synchronized都是
乐观锁 vs 悲观锁? 乐观:不加锁,更新检查版本/CAS;悲观:每次加锁;读多用乐观,写多用悲观 适用场景选择
公平锁 vs 非公平锁? 公平:FIFO,无饥饿但性能低;非公平:可能插队,性能高(默认推荐) 吞吐量 vs 公平性权衡
读写锁特点? 读-读并发,读-写/写-写互斥;支持锁降级(写→读),不支持锁升级 ReentrantReadWriteLock
AQS核心思想? state(状态)+ CLH队列(FIFO等待队列)+ 独占/共享模式 AbstractQueuedSynchronizer
什么是死锁?如何避免? 四条件:互斥、请求保持、不剥夺、循环等待;避免:统一顺序、tryLock超时、避免锁升级 死锁检测与预防
阻塞队列 核心方法分类? 抛出异常(add/remove)、特殊值(offer/poll)、阻塞(put/take)、超时(offer/poll+time) 根据场景选择方法
Array vs LinkedBlockingQueue? Array:单锁、有界、预分配;Linked:双锁分离、默认无界、动态分配 吞吐量与内存权衡
SynchronousQueue特点? 零容量,直接传递(handoff);公平(队列)/非公平(栈)模式 极高吞吐量场景
DelayQueue应用场景? 元素需实现Delayed,到期才能取出;用于:定时任务、订单超时、缓存过期 Leader-Follower优化等待
综合设计 高并发秒杀系统(线程安全)? 乐观锁/CAS扣库存、限流削峰、异步消息队列、读写分离、本地缓存 多层防护策略
ThreadLocal原理与内存泄漏? ThreadLocalMap弱引用key,但value强引用;必须finally中remove() 线程池复用场景风险
CompletableFuture优势? 支持回调链式组合、异常处理、取消超时;替代Future阻塞获取 函数式异步编程

快速记忆口诀

场景 口诀
锁升级 无偏轻重重(无锁→偏向→轻量级→重量级)
并发特性 原可序(原子性、可见性、有序性)
volatile 可序不可原(可见性+有序性,无原子性)
阻塞队列选择 Arr有界单锁,Link无界双锁,Sync零容直传,Delay到期才取
死锁预防 统顺超(统一顺序、tryLock超时、避免锁升级)
相关推荐
SimonKing2 小时前
IntelliJ IDEA AI Assistant 携带OpenCode保姆级安装教程来了
java·后端·程序员
XiYang-DING2 小时前
【Java SE】sealed关键字
java·开发语言·python
Flittly3 小时前
【SpringAIAlibaba新手村系列】(8)持久化会话与 Redis 内存管理
java·人工智能·spring boot·spring·ai
祈澈菇凉3 小时前
Next.js + OpenAI API 跑通一个带流式输出的聊天机器人
开发语言·javascript·机器人
东离与糖宝3 小时前
Java 干掉 Python 垄断!LangChain4j + PgVector 本地知识库开发全流程
java·人工智能
lsx2024063 小时前
MySQL 删除数据表
开发语言
前端程序猿i3 小时前
纯JS 导出 Excel 工具
开发语言·javascript·excel
东离与糖宝3 小时前
OpenClaw 企业级实战:Java 微服务集成 AI 智能体,自动处理业务流
java·人工智能
沐知全栈开发3 小时前
XML Schema 复合类型 - 仅含元素
开发语言