Java多线程并发核心进阶|Thread/Runnable、synchronized、Lock/AQS、volatile、ThreadLocal、线程池、并发工具、Fork/Join

并发编程真正难的不是背概念,而是理解线程共享、锁竞争、内存可见性和线程池治理这些机制如何一起决定系统吞吐与稳定性。

多线程并发是Java后端核心重难点、面试压轴考点,也是服务端高并发、高可用架构的底层基石。日常业务异步处理、接口限流、批量任务、并行计算、框架底层(Spring、MyBatis、线程池)全部依赖多线程机制实现。

本文全覆盖核心考点:线程创建(Thread/Runnable)、synchronized锁、Lock&AQS、volatile、ThreadLocal、线程池Executor、CountDownLatch/CyclicBarrier并发工具、Fork/Join分支合并,搭配底层原理、完整实战代码、高频坑点、工程规范、面试标准答案,零基础可学、面试可直接背诵、项目可直接落地。

一、多线程核心基础概述

1.1 进程与线程区别

  • 进程:系统资源分配的最小单位,独立内存、独立资源,进程间隔离性强、通信成本高

  • 线程:进程内的执行单元、CPU调度最小单位,共享进程资源,轻量级、切换成本低

1.2 并发与并行

  • 并发:单核CPU快速切换多个线程,宏观同时执行、微观交替执行

  • 并行:多核CPU同时执行多个线程,真正意义上的同时运行

1.3 线程五大生命周期(面试必背)

新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked/Waiting) → 终止(Terminated)

二、线程创建方式:Thread vs Runnable

Java最基础的两种线程创建方式,也是所有多线程的起点,核心区别在于是否支持资源共享、是否面向接口编程

2.1 继承Thread类

继承Thread类,重写run()方法,单继承限制,无法复用资源

// 继承Thread类创建线程
public class ThreadDemo extends Thread {
    @Override
    public void run() {
        // 线程执行任务
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

2.2 实现Runnable接口(推荐)

实现Runnable接口,重写run()方法,规避单继承限制,支持多线程共享任务资源,工程开发首选。

// 实现Runnable接口
public class RunnableDemo implements Runnable {
    private int count = 10;

    @Override
    public void run() {
        while (count > 0) {
            System.out.println(Thread.currentThread().getName() + " 剩余数量:" + count--);
        }
    }

    public static void main(String[] args) {
        // 同一个任务对象,多个线程共享资源
        RunnableDemo task = new RunnableDemo();
        new Thread(task, "线程A").start();
        new Thread(task, "线程B").start();
    }
}

2.3 Thread与Runnable核心区别

  • 继承Thread:不能继承其他类,每个线程独立资源,无法共享任务

  • 实现Runnable:支持多实现、资源共享、解耦任务与线程,符合面向接口编程

三、同步锁:synchronized(内置锁)

synchronized是Java原生内置锁,解决多线程共享资源的线程安全问题,保证原子性、可见性、有序性,无需手动解锁,底层由JVM实现。

3.1 三种使用方式

  1. 修饰普通方法:锁当前对象this

  2. 修饰静态方法:锁当前类Class对象(全局锁)

  3. 修饰代码块:锁自定义对象(细粒度锁,推荐)

3.2 实战代码(解决线程不安全)

public class SyncDemo {
    private static int num = 0;

    // 同步代码块,保证计数安全
    public static void add() {
        synchronized (SyncDemo.class) {
            num++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 开启10个线程累加1000次
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        Thread.sleep(2000);
        // 最终结果固定10000
        System.out.println("累加结果:" + num);
    }
}

3.3 底层锁升级机制(面试核心)

JDK1.6优化后,synchronized不再是重量级锁,具备锁升级机制,性能大幅提升:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

  • 偏向锁:单线程竞争,无锁开销

  • 轻量级锁:少量线程交替竞争,自旋CAS

  • 重量级锁:大量线程竞争,阻塞等待

四、Lock锁与AQS核心原理

Lock是JDK5提供的显式锁,功能比synchronized更强大、灵活,支持超时锁、可中断锁、公平锁,底层基于AQS实现

4.1 AQS核心原理

AQS(AbstractQueuedSynchronizer):抽象队列同步器,是所有Lock锁、并发工具的底层基石。

  • 核心变量:state状态值(0无锁、1加锁、&gt;1重入锁)

  • 核心结构:双向阻塞队列,存储所有等待线程

  • 核心机制:CAS自旋抢锁 + 队列阻塞排队

4.2 ReentrantLock可重入锁实战

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {
    private static int num = 0;
    // 创建显式锁
    private static final Lock LOCK = new ReentrantLock();

    public static void add() {
        LOCK.lock(); // 手动加锁
        try {
            num++;
        } finally {
            LOCK.unlock(); // 手动解锁,必须放finally
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println("累加结果:" + num);
    }
}

4.3 synchronized vs ReentrantLock 终极对比

对比维度 synchronized ReentrantLock
锁类型 内置隐式锁,JVM管控 显式锁,代码手动管控
锁机制 自动加锁解锁 手动lock/unlock,必须finally解锁
公平锁 默认非公平,不支持修改 支持公平/非公平锁切换
高级功能 可中断、超时锁、条件队列
性能 JDK1.6后优化,性能持平 高并发场景性能更优

五、volatile 关键字(轻量级并发)

volatile是轻量级并发关键字,核心作用:保证可见性、禁止指令重排序不保证原子性,无锁开销,适合状态标记、读写分离场景。

5.1 三大核心特性

  • 保证可见性:一个线程修改变量,其他线程立即感知最新值,禁止缓存

  • 禁止指令重排序:遵循内存屏障规则,解决DCL单例模式重排序BUG

  • 不保证原子性:复合操作(i++)非线程安全,无法替代锁

5.2 可见性实战代码

public class VolatileDemo {
    // 不加volatile,程序死循环;加volatile,主线程可见变更,程序结束
    private static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {
                // 循环监听flag状态
            }
        }).start();

        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程修改flag为false,子线程结束循环");
    }
}

5.3 高频面试坑点

  • volatile只能修饰变量,不能修饰方法、类

  • 无法保证原子性,i++、复合运算仍存在线程安全问题

  • 适合读多写少场景,纯粹状态标记

六、ThreadLocal 线程本地变量

ThreadLocal:线程本地存储,为每个线程单独分配独立变量副本,线程间数据完全隔离,彻底解决多线程变量共享冲突问题。

6.1 核心原理

每个Thread内部维护一个ThreadLocalMap,key为ThreadLocal对象,value为线程私有数据,不同线程的Map相互独立,互不干扰。

6.2 实战代码

public class ThreadLocalDemo {
    // 创建线程本地变量
    private static final ThreadLocal<String> LOCAL = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            LOCAL.set("线程A数据");
            System.out.println(Thread.currentThread().getName() + ":" + LOCAL.get());
        }, "线程A").start();

        new Thread(() -> {
            LOCAL.set("线程B数据");
            System.out.println(Thread.currentThread().getName() + ":" + LOCAL.get());
        }, "线程B").start();
    }
}

6.3 核心坑点与最佳实践(工程必看)

  • 内存泄漏:线程池场景线程复用,不remove会导致数据残留、内存溢出

  • 使用规范:使用完毕必须调用 LOCAL\.remove\(\) 清除数据

  • 适用场景:用户上下文、请求参数、事务信息、独立线程缓存

七、线程池 Executor(工程核心)

线程池是企业开发必须使用的线程创建方式,避免频繁创建销毁线程的开销,统一管理线程、管控并发、提升系统吞吐量。

7.1 核心优势

  • 降低资源消耗:复用线程,避免频繁创建销毁

  • 提高响应速度:线程就绪,可直接执行任务

  • 统一管控:统一调度、监控、拒绝策略,避免并发爆炸

7.2 七大核心参数(面试必考)

  1. 核心线程数corePoolSize:常驻线程,空闲不销毁

  2. 最大线程数maximumPoolSize:线程池允许的最大线程数量

  3. 空闲超时时间keepAliveTime:非核心线程空闲超时销毁时间

  4. 时间单位unit:超时时间单位

  5. 任务队列workQueue:存储等待执行的任务

  6. 线程工厂threadFactory:创建线程,自定义线程名

  7. 拒绝策略handler:任务满负荷时的拒绝规则

7.3 手动创建线程池(阿里规范,禁止Executors)

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 手动创建线程池(工程标准写法)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交任务
        for (int i = 0; i < 8; i++) {
            int index = i;
            pool.execute(() -> {
                System.out.println("执行任务:" + index + ",线程:" + Thread.currentThread().getName());
            });
        }
        // 关闭线程池
        pool.shutdown();
    }
}

7.4 四大拒绝策略

  • AbortPolicy:默认策略,直接抛出异常

  • CallerRunsPolicy:调用者线程执行任务

  • DiscardPolicy:直接丢弃任务,无异常

  • DiscardOldestPolicy:丢弃队列最旧任务,执行新任务

八、并发工具类:CountDownLatch / CyclicBarrier

JUC并发工具用于多线程任务协同、批量任务控制,是业务异步、批量处理高频工具。

8.1 CountDownLatch(倒计时计数器)

特性:一次性计数器,线程倒计时归零后,唤醒等待线程,不可复用

场景:主线程等待所有子线程任务执行完毕再执行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 计数器初始值5
        CountDownLatch latch = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 任务执行完成");
                latch.countDown(); // 计数器-1
            }).start();
        }
        latch.await(); // 主线程等待计数器归零
        System.out.println("所有子线程执行完毕,主线程收尾");
    }
}

8.2 CyclicBarrier(循环栅栏)

特性:可循环复用屏障,所有线程到达屏障点后统一放行,支持重复使用

场景:多线程分批任务、多人组队同步执行。

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 凑齐5个线程统一执行,可循环复用
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {
            System.out.println("凑齐5个线程,统一放行执行任务");
        });

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 到达屏障点");
                    barrier.await(); // 等待凑齐数量放行
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

8.3 两者核心区别

  • CountDownLatch:一次性、递减归零、主线程等待子线程

  • CyclicBarrier:可循环复用、凑数放行、线程互相等待

九、Fork/Join 分支合并框架

Fork/Join是JDK7推出的并行计算框架,核心思想:分而治之,将大任务拆分为小任务并行执行,最终合并结果,适合海量数据计算、递归任务。

9.1 核心原理

  • Fork拆分:大任务递归拆分为独立小任务

  • Join合并:等待所有子任务执行完毕,合并最终结果

  • 工作窃取算法:空闲线程窃取繁忙线程任务,最大化利用CPU

9.2 实战代码(1-100累加计算)

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

// 自定义累加任务
class SumTask extends RecursiveTask<Integer> {
    private int start;
    private int end;

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

    @Override
    protected Integer compute() {
        // 阈值:差值小于10直接计算
        if (end - start <= 10) {
            int sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }
        // 拆分任务
        int mid = (start + end) / 2;
        SumTask left = new SumTask(start, mid);
        SumTask right = new SumTask(mid + 1, end);
        left.fork();
        right.fork();
        // 合并结果
        return left.join() + right.join();
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Integer> task = pool.submit(new SumTask(1, 100));
        System.out.println("1-100累加结果:" + task.get());
        pool.shutdown();
    }
}

十、多线程工程坑点 &amp; 最佳实践

  • 禁止手动创建线程:所有业务线程统一使用线程池管理

  • 禁止使用Executors工具类:避免无界队列、超大线程数导致OOM

  • volatile不做原子运算:仅用于状态标记,复合运算必须加锁

  • ThreadLocal必须remove:线程池复用线程,防止内存泄漏和数据串扰

  • 锁粒度最小化:优先同步代码块,减少锁持有时间,提升并发性能

  • 高并发优先Lock:竞争激烈场景ReentrantLock性能优于synchronized

  • Fork/Join适合密集计算:不适合IO阻塞任务,浪费CPU资源

十一、全文核心总结(面试必背)

  • 线程创建:Thread继承、Runnable实现,优先Runnable解耦共享

  • synchronized:内置锁,自动加解锁,锁升级机制,保证三大特性

  • Lock&amp;AQS:显式锁,基于队列同步器,功能更强、高并发更优

  • volatile:轻量级,保证可见性、禁止重排,不保证原子性

  • ThreadLocal:线程数据隔离,解决线程安全,注意内存泄漏

  • 线程池:七大参数、四大拒绝策略,统一线程管理,工程必备

  • 并发工具:CountDownLatch一次性倒计时、CyclicBarrier循环栅栏

  • Fork/Join:分而治之、工作窃取,适合并行批量计算任务

十二、高频面试简答题

  • 线程和进程区别? 进程是资源分配单位,线程是CPU调度单位,线程共享进程资源、开销更低。

  • synchronized和Lock区别? 前者隐式自动锁,后者显式手动锁,Lock支持公平锁、超时锁、可中断,功能更强大。

  • volatile能否保证线程安全? 不能,仅保证可见性和有序性,不保证原子性,复合操作仍不安全。

  • ThreadLocal原理与坑点? 线程独立Map存储数据,线程隔离;线程池场景不清除会导致内存泄漏、数据串扰。

  • 为什么禁止使用Executors? 部分线程池使用无界队列,任务堆积会导致内存溢出,无法管控并发数量。

  • CountDownLatch和CyclicBarrier区别? 前者一次性倒计时,主线程等待子线程;后者可循环复用,线程互相等待。

  • AQS核心原理? 基于state状态值+双向阻塞队列,CAS抢锁、失败入队阻塞,是JUC锁的底层核心。

  • Fork/Join工作窃取算法? 空闲线程主动窃取其他线程未执行任务,均衡负载,提升CPU利用率。

(注:文档部分内容可能由 AI 生成)

本文总结

  • Thread、Runnable、synchronized 和 Lock 只是并发编程的表层 API,真正关键的是它们背后的共享资源模型、AQS 队列和竞争策略。
  • volatile、ThreadLocal 与线程池分别解决可见性、线程隔离和统一调度问题,三者经常一起出现在真实业务链路里。
  • CountDownLatch、CyclicBarrier 和 Fork/Join 代表的是并发协作、分批同步和并行计算三类不同问题,不该混着记。
GYSTACK 文章文末广告 硅云云服务器活动 适合个人项目、轻量建站和出海业务部署。
后浪云移动端信息流广告 后浪云主机服务 适合长期部署、独立站和海外机房需求。