一、并发基础核心概念

1.1 进程与线程

核心定义
  • 进程:程序的运行实例,负责加载指令、管理内存和 IO,是资源分配的最小单位(如记事本、浏览器运行时即为一个进程)。
  • 线程:进程内的指令流,是CPU 调度的最小单位,一个进程可包含多个线程(如浏览器的多个标签页对应多个线程)。
关键区别
对比维度 进程 线程
独立性 相互独立 共享进程资源
通信成本 高(IPC / 网络协议) 低(共享内存)
上下文切换 成本高 成本低

1.2 并行与并发

核心定义(Rob Pike 经典描述)
  • 并发(Concurrent):同一时间应对多件事(单核 CPU 通过时间片切换实现,微观串行,宏观并行)。
  • 并行(Parallel):同一时间动手做多件事(多核 CPU 同时调度多个线程)。
生活案例
  • 并发:家庭主妇独自做饭、打扫卫生、喂奶(轮流交替)。
  • 并行:家庭主妇 + 3 个保姆,每人负责一件事(互不干扰)。
技术本质
  • 单核 CPU:仅支持并发(线程轮流占用 CPU 时间片,切换速度快至人类无法感知)。
  • 多核 CPU:支持并行(多个核心同时运行不同线程)。

1.3 并发的核心应用场景

  1. 异步调用:避免主线程阻塞(如视频格式转换、Tomcat 异步 Servlet、UI 程序后台任务)。
  2. 提高效率:多核 CPU 下并行计算(如 3 个耗时任务并行执行,总耗时取决于最长任务)。
  3. 资源统筹:合理分配线程执行任务(如烧水泡茶时,利用烧水时间洗茶壶、拿茶叶)。

二、Java 线程核心操作

2.1 线程创建的三种方式

方式 1:直接继承 Thread
// 推荐指定线程名,便于调试
Thread t1 = new Thread("t1") {
    @Override
    public void run() {
        log.debug("线程t1执行"); // 输出:19:19:00 [t1] c.ThreadStarter - 线程t1执行
    }
};
t1.start(); // 启动线程(不可重复调用)
方式 2:Runnable 配合 Thread(解耦线程与任务)
// 1. 定义任务(线程要执行的逻辑)
Runnable task2 = () -> log.debug("线程t2执行"); // JDK8+ lambda简化
// 2. 创建线程(参数1:任务,参数2:线程名)
Thread t2 = new Thread(task2, "t2");
t2.start(); // 输出:19:19:00 [t2] c.ThreadStarter - 线程t2执行
方式 3:FutureTask 处理有返回值的任务
// 1. 创建带返回值的任务(Callable接口)
FutureTask<Integer> task3 = new FutureTask<>(() -> {
    log.debug("线程t3执行");
    return 100; // 返回结果
});
// 2. 启动线程
new Thread(task3, "t3").start();
// 3. 主线程同步等待结果(阻塞)
Integer result = task3.get();
log.debug("线程t3返回结果:{}", result); // 输出:19:22:27 [main] c.ThreadStarter - 线程t3返回结果:100
三种方式对比
方式 优点 缺点 适用场景
Thread 继承 代码简洁 单继承限制 简单任务
Runnable 解耦线程与任务 无返回值 多线程共享任务
FutureTask 支持返回值 需处理异常 需获取任务结果(如并行计算)

2.2 线程核心 API 详解

常用方法(重点)
方法名 功能 关键注意点
start() 启动线程(进入就绪状态) 仅能调用 1 次,重复调用抛异常
run() 线程执行的任务逻辑 直接调用不会启动新线程(主线程执行)
join() 等待线程执行完毕 可指定超时时间(join(long n)
interrupt() 打断线程 打断阻塞线程抛InterruptedException
sleep(long n) 休眠 n 毫秒 不释放锁,推荐用TimeUnit.sleep()
yield() 让出 CPU 使用权 仅为提示,调度器可忽略
getState() 获取线程状态 返回 6 种状态(NEW/RUNNABLE 等)
核心 API 实

1. start 与 run 的区别

// 错误:直接调用run(主线程执行)
Thread t1 = new Thread("t1") {
    @Override
    public void run() {
        log.debug(Thread.currentThread().getName()); // 输出main
    }
};
t1.run();

// 正确:调用start(新线程执行)
t1.start(); // 输出t1

2.  join 等待线程完成

static int r = 0;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        sleep(1000); // 模拟耗时操作
        r = 10;
    });
    t1.start();
    t1.join(); // 等待t1执行完毕
    log.debug("r的值:{}", r); // 输出10(而非初始0)
}

3. interrupt 打断线程

// 打断阻塞线程(sleep)
Thread t1 = new Thread(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        log.debug("线程被打断");
        return;
    }
}, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt(); // 打断t1的sleep

2.3 线程状态(Java API 层面)

6 种状态及转换
状态 含义 转换场景
NEW 线程未启动 创建线程后未调用start()
RUNNABLE 就绪 / 运行 / IO 阻塞 调用start()
BLOCKED 等待锁 竞争synchronized锁失败
WAITING 无超时等待 调用wait()/join()/park()
TIMED_WAITING 有超时等待 调用sleep(long)/wait(long)
TERMINATED 线程执行完毕 任务执行完成或异常终止
状态转换流程图
NEW → RUNNABLE(start())
RUNNABLE ↔ BLOCKED(竞争synchronized锁)
RUNNABLE ↔ WAITING(wait()/park()等)
RUNNABLE ↔ TIMED_WAITING(sleep()/wait(long)等)
RUNNABLE → TERMINATED(任务完成)

2.4 主线程与守护线程

核心定义
  • 守护线程:依赖非守护线程,当所有非守护线程结束,守护线程强制终止(无需执行完)。
  • 非守护线程:默认线程类型(如主线程),需等待自身执行完毕。
实战示例
log.debug("主线程启动");
Thread daemonThread = new Thread(() -> {
    log.debug("守护线程启动");
    Thread.sleep(2000); // 模拟耗时操作
    log.debug("守护线程执行完毕"); // 不会输出(主线程1秒后结束)
}, "daemon");
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
Thread.sleep(1000);
log.debug("主线程执行完毕");
常见应用
  • 垃圾回收器(GC)线程:守护线程(JVM 退出时无需等待 GC 完成)。
  • Tomcat 的 Acceptor/Poller 线程:守护线程(Tomcat 关闭时无需等待当前请求处理)。

三、共享模型之管程(synchronized 与 Lock)

3.1 共享资源的线程安全问题

问题本质

多线程对共享资源进行读写操作时,指令交错导致结果不可预测(竞态条件)。

经典案例:静态变量自增自减
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
    // 线程1:自增5000次
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) counter++;
    }, "t1");
    // 线程2:自减5000次
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) counter--;
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("counter结果:{}", counter); // 结果可能为正数、负数、0
}
问题分析(字节码层面)

counter++对应的字节码指令(非原子操作):

  1. getstatic:读取主存中counter的值到工作内存。
  2. iconst_1:准备常量 1。
  3. iadd:工作内存中自增。
  4. putstatic:将结果写回主存。

多线程下指令交错执行,导致最终结果错误。

3.2 synchronized 解决方案(对象锁)

核心原理

通过互斥锁保证同一时刻仅有一个线程进入临界区(读写共享资源的代码块),避免指令交错。

语法格式

1. 代码块形式(推荐,锁粒度可控)

static final Object lock = new Object(); // 锁对象(任意对象均可)
static int counter = 0;

// 临界区代码
synchronized (lock) {
    counter++; // 原子操作
}

2. 成员方法形式(锁当前对象 this)

public synchronized void increment() {
    counter++;
}

3. 静态方法形式(锁类对象 Class)

public static synchronized void decrement() {
    counter--;
}
解决线程安全问题(修改后案例)
static final Object lock = new Object();
static int counter = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            synchronized (lock) {
                counter++;
            }
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            synchronized (lock) {
                counter--;
            }
        }
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("counter结果:{}", counter); // 稳定输出0
}
关键注意点
  • 锁对象必须唯一(多线程竞争同一把锁才有效)。
  • 临界区代码应尽量精简(减少锁持有时间,提高并发度)。
  • synchronized自动释放锁(异常或代码执行完毕时)。

3.3 线程八锁(synchronized 锁对象面试题)

核心考点:判断synchronized锁住的对象
情况 代码示例 执行结果 结论
1 两个同步成员方法,同一对象调用 12 或 21 锁 this,同一对象互斥
2 同步成员方法 + 非同步方法,同一对象调用 3 先输出,再 12 或 21 非同步方法不参与锁竞争
3 两个同步静态方法,不同对象调用 1s 后 12 或 21 锁 Class 对象,全局互斥
4 同步成员方法 + 同步静态方法,同一对象调用 2 先输出,1s 后 1 锁对象不同(this vs Class),不互斥

3.4 Lock 接口(synchronized 增强版)

Lock 与 synchronized 对比
特性 synchronized Lock(ReentrantLock)
可中断 是(lockInterruptibly()
超时获取 是(tryLock(long)
公平锁 是(构造函数指定true
多条件变量 是(newCondition()
手动释放 否(自动) 是(必须在 finally 中unlock()
ReentrantLock 核心用法
// 1. 创建Lock对象(公平锁)
ReentrantLock lock = new ReentrantLock(true);
// 2. 条件变量(实现线程间通信)
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 3. 加锁与释放锁(必须在finally中释放)
lock.lock();
try {
    // 临界区代码
    while (条件不满足) {
        notEmpty.await(); // 等待
    }
    // 业务逻辑
    notFull.signal(); // 唤醒其他线程
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock(); // 释放锁
}
可中断锁实战
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
    log.debug("t1启动,尝试获取锁");
    try {
        lock.lockInterruptibly(); // 可被打断的锁
    } catch (InterruptedException e) {
        log.debug("t1获取锁时被打断");
        return;
    }
    try {
        log.debug("t1获取锁成功");
    } finally {
        lock.unlock();
    }
}, "t1");

lock.lock(); // 主线程先获取锁
log.debug("主线程获取锁成功");
t1.start();
Thread.sleep(1000);
t1.interrupt(); // 打断t1的锁等待
log.debug("主线程执行打断");
lock.unlock();

3.5 线程间通信(wait/notify vs Condition)

1. Object 的 wait/notify(synchronized 配套)
static final Object lock = new Object();
static boolean hasCigarette = false;

public static void main(String[] args) {
    // 等待线程
    new Thread(() -> {
        synchronized (lock) {
            while (!hasCigarette) { // 避免虚假唤醒,用while而非if
                try {
                    log.debug("没烟,等待...");
                    lock.wait(); // 释放锁,进入等待队列
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("有烟了,开始干活");
        }
    }, "小南").start();

    // 唤醒线程
    new Thread(() -> {
        synchronized (lock) {
            hasCigarette = true;
            log.debug("烟到了,唤醒等待线程");
            lock.notifyAll(); // 唤醒所有等待线程
        }
    }, "送烟的").start();
}
2. Condition(Lock 配套,多条件精准唤醒)
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigarette = lock.newCondition(); // 等烟的条件
static Condition waitBreakfast = lock.newCondition(); // 等早餐的条件
static boolean hasCigarette = false;
static boolean hasBreakfast = false;

public static void main(String[] args) {
    // 等烟的线程
    new Thread(() -> {
        lock.lock();
        try {
            while (!hasCigarette) {
                waitCigarette.await();
            }
            log.debug("等到烟,干活");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }).start();

    // 等早餐的线程
    new Thread(() -> {
        lock.lock();
        try {
            while (!hasBreakfast) {
                waitBreakfast.await();
            }
            log.debug("等到早餐,干活");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }).start();

    // 送早餐(精准唤醒等早餐的线程)
    new Thread(() -> {
        lock.lock();
        try {
            hasBreakfast = true;
            waitBreakfast.signal();
        } finally {
            lock.unlock();
        }
    }).start();
}

3.6 活跃性问题(死锁 / 活锁 / 饥饿)

1. 死锁(最常见)
死锁条件
  • 资源互斥
  • 持有并等待
  • 不可剥夺
  • 循环等待
死锁示例
Object A = new Object();
Object B = new Object();

// 线程1:持有A,等待B
Thread t1 = new Thread(() -> {
    synchronized (A) {
        log.debug("t1持有A,等待B");
        Thread.sleep(1000);
        synchronized (B) {
            log.debug("t1持有A和B");
        }
    }
}, "t1");

// 线程2:持有B,等待A
Thread t2 = new Thread(() -> {
    synchronized (B) {
        log.debug("t2持有B,等待A");
        Thread.sleep(1000);
        synchronized (A) {
            log.debug("t2持有B和A");
        }
    }
}, "t2");

t1.start();
t2.start();
死锁排查
  • 工具:jconsole(图形化)、jstack(命令行)。
  • 命令:jps获取 PID → jstack PID查看死锁信息。
死锁避免
  • 按固定顺序获取锁(如先获取 A 再获取 B)。
  • 超时获取锁(tryLock(long))。
  • 避免一次性获取多把锁。
2. 活锁与饥饿
  • 活锁:线程互相修改对方结束条件,无法终止(如线程 1 减 count,线程 2 加 count,永远无法满足退出条件)。
  • 饥饿:低优先级线程长期得不到 CPU 调度(如高优先级线程持续占用 CPU)。

四、共享模型之内存(JMM)

4.1 JMM 核心目标

解决多线程下的三大问题:

  • 原子性:指令不可分割(避免线程切换导致指令交错)。
  • 可见性:一个线程对共享变量的修改,其他线程能立即看到(避免 CPU 缓存导致的旧值读取)。
  • 有序性:避免指令重排导致的执行顺序混乱。

4.2 可见性问题

问题示例(退不出的循环)
static boolean run = true; // 未加volatile

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
        while (run) {
            // 循环体为空
        }
    });
    t.start();
    Thread.sleep(1000);
    run = false; // 主线程修改run,t线程看不到,无法退出
}
问题原因
  • 线程 t 频繁读取run,JIT 编译器将其缓存到 CPU 高速缓存,主线程修改后,t 线程仍读取缓存中的旧值。
解决方案:volatile 关键字
static volatile boolean run = true; // 加volatile
volatile 核心作用
  • 强制线程读写变量时直接操作主存(不经过 CPU 缓存)。
  • 禁止指令重排(保证有序性)。
注意:volatile 不保证原子性
static volatile int i = 0;

public static void main(String[] args) throws InterruptedException {
    // 1000个线程,每个自增1000次
    for (int j = 0; j < 1000; j++) {
        new Thread(() -> {
            for (int k = 0; k < 1000; k++) i++;
        }).start();
    }
    Thread.sleep(2000);
    log.debug("i的值:{}", i); // 结果小于1000000(原子性问题)
}

4.3 有序性问题(指令重排)

指令重排定义

JVM 在不影响单线程正确性的前提下,调整指令执行顺序(如i=1; j=2可能重排为j=2; i=1)。

多线程下的问题示例
int num = 0;
boolean ready = false;

// 线程1
public void actor1(I_Result r) {
    if (ready) {
        r.r1 = num + num; // 可能为0(指令重排导致)
    } else {
        r.r1 = 1;
    }
}

// 线程2
public void actor2(I_Result r) {
    num = 2;
    ready = true; // 可能重排到num=2之前执行
}
解决方案
  • volatile修饰ready(禁止指令重排)。
  • synchronized/Lock(保证原子性的同时保证有序性)。

4.4 happens-before 规则(可见性 / 有序性保障)

JMM 定义的一套规则,保证线程间操作的可见性:

  1. 锁解锁 → 后续加锁可见(synchronized/Lock)。
  2. volatile写 → 后续volatile读可见。
  3. 线程start()前的写 → 线程内读可见。
  4. 线程结束前的写 → 其他线程join()后读可见。
  5. 传递性:A→B 且 B→C,则 A→C。

五、共享模型之无锁(CAS)

5.1 CAS 核心原理

定义

Compare And Swap(比较并交换),基于乐观锁思想:

  • 核心操作:compareAndSet(预期值, 新值)
  • 逻辑:如果当前值等于预期值,则更新为新值(原子操作),否则重试。
底层实现

CPU 的lock cmpxchg指令(单核 / 多核均保证原子性),多核下通过总线锁定保证操作原子性。

CAS 实战(原子类 AtomicInteger)
public class AccountSafe implements Account {
    private AtomicInteger balance;

    public AccountSafe(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int prev = balance.get(); // 获取当前值
            int next = prev - amount; // 计算新值
            // CAS更新:预期值prev,新值next
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
        // 简化写法:balance.addAndGet(-amount);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }
}

5.2 原子类家族

1. 基本类型原子类
  • AtomicInteger/AtomicLong/AtomicBoolean:解决基本类型的原子操作。
  • 核心方法:getAndIncrement()(i++)、incrementAndGet()(++i)、compareAndSet()
2. 引用类型原子类
  • AtomicReference:原子操作引用类型。
  • AtomicStampedReference:解决 ABA 问题(加版本号)。
  • AtomicMarkableReference:解决 ABA 问题(加标记,不关心修改次数)。
3. ABA 问题及解决
ABA 问题示例
static AtomicReference<String> ref = new AtomicReference<>("A");

public static void main(String[] args) throws InterruptedException {
    // 线程1:A→B→A
    new Thread(() -> {
        String prev = ref.get();
        log.debug("修改A→B");
        ref.compareAndSet(prev, "B");
        log.debug("修改B→A");
        ref.compareAndSet("B", "A");
    }, "t1").start();

    Thread.sleep(1000);
    // 线程2:A→C(无法感知A被修改过)
    String prev = ref.get();
    log.debug("修改A→C:{}", ref.compareAndSet(prev, "C")); // 输出true
}
解决方案:AtomicStampedReference
// 初始化:值A,版本号0
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

public static void main(String[] args) throws InterruptedException {
    // 线程1:A→B→A(版本号从0→1→2)
    new Thread(() -> {
        int stamp = ref.getStamp(); // 获取当前版本号0
        log.debug("当前版本:{}", stamp);
        ref.compareAndSet("A", "B", stamp, stamp + 1); // 0→1
        ref.compareAndSet("B", "A", 1, 2); // 1→2
    }, "t1").start();

    Thread.sleep(1000);
    // 线程2:用版本号0修改A→C(失败)
    int stamp = ref.getStamp(); // 此时版本号为2
    log.debug("修改A→C:{}", ref.compareAndSet("A", "C", 0, 1)); // 输出false
}

5.3 原子累加器(LongAdder)

为什么比 AtomicLong 快?
  • AtomicLong:高并发下 CAS 重试频繁,效率低。
  • LongAdder:分段累加(多个 Cell 单元),减少 CAS 竞争,最后汇总结果。
实战对比
// LongAdder累加(高并发高效)
LongAdder adder = new LongAdder();
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        for (int j = 0; j < 10000; j++) {
            adder.increment();
        }
    }).start();
}

// AtomicLong累加(高并发低效)
AtomicLong atomicLong = new AtomicLong();
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
        for (int j = 0; j < 10000; j++) {
            atomicLong.incrementAndGet();
        }
    }).start();
}

六、共享模型之不可变

6.1 不可变类定义

  • 类的状态不可修改(属性为final)。
  • 没有修改状态的方法(仅提供读操作)。
  • 类声明为final(避免子类破坏不可变性)。
经典示例:String 类
public final class String implements Serializable {
    private final char value[]; // final属性,不可修改
    // 无修改value的方法(replace/substring等方法返回新String)
}

6.2 不可变类的线程安全

不可变类天然线程安全(无状态修改,无需锁),例如:

  • StringIntegerLong等包装类。
  • Java 8 的DateTimeFormatter(线程安全,替代SimpleDateFormat)。
实战:日期格式化(线程安全)
// DateTimeFormatter是不可变类,线程安全
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        LocalDate date = dtf.parse("2024-01-01", LocalDate::from);
        log.debug("{}", date);
    }).start();
}

6.3 无状态设计

  • 无状态类:没有成员变量(状态信息),天然线程安全。
  • 应用场景:Servlet(避免设置成员变量)、工具类(如StringUtils)。

七、JUC 核心工具实战

7.1 线程池(ThreadPoolExecutor)

线程池核心作用
  • 复用线程(避免线程创建 / 销毁的开销)。
  • 控制线程数量(防止资源耗尽)。
  • 管理任务队列(缓冲任务)。
核心参数
public ThreadPoolExecutor(
    int corePoolSize, // 核心线程数(常驻线程)
    int maximumPoolSize, // 最大线程数(核心+救急线程)
    long keepAliveTime, // 救急线程空闲时间
    TimeUnit unit, // 时间单位
    BlockingQueue<Runnable> workQueue, // 任务队列
    ThreadFactory threadFactory, // 线程工厂(命名线程)
    RejectedExecutionHandler handler // 拒绝策略
)
常见线程池(Executors 工具类)
线程池类型 核心参数 适用场景
newFixedThreadPool 核心 = 最大线程数,无救急线程 任务量固定,耗时较长
newCachedThreadPool 核心 = 0,最大 = Integer.MAX_VALUE 任务密集,执行时间短
newSingleThreadExecutor 核心 = 最大 = 1 单线程串行执行任务
newScheduledThreadPool 核心线程数固定 定时 / 周期性任务
线程池工作流程
  1. 提交任务 → 核心线程未满 → 创建核心线程执行。
  2. 核心线程已满 → 任务队列未满 → 放入队列。
  3. 队列已满 → 未达最大线程数 → 创建救急线程执行。
  4. 达最大线程数 → 执行拒绝策略。
拒绝策略
策略 作用
AbortPolicy RejectedExecutionException(默认)
CallerRunsPolicy 调用者执行任务
DiscardPolicy 放弃当前任务
DiscardOldestPolicy 放弃队列中最早的任务
实战:自定义线程池
// 1. 创建线程池(核心2,最大5,队列容量10)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2,
    5,
    60,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);

// 2. 提交10个任务
for (int i = 0; i < 10; i++) {
    int finalI = i;
    pool.submit(() -> {
        log.debug("执行任务{}", finalI);
        Thread.sleep(1000);
    });
}

// 3. 关闭线程池
pool.shutdown(); // 等待任务执行完毕后关闭
// pool.shutdownNow(); // 立即关闭,中断正在执行的任务

7.2 并发集合(java.util.concurrent)

常用并发集合对比
集合类型 线程安全实现 适用场景
ConcurrentHashMap CAS + 分段锁 高并发键值对存储
CopyOnWriteArrayList 写入时拷贝数组 读多写少(如配置列表)
BlockingQueue 锁 + 条件变量 生产者 - 消费者模型
ConcurrentLinkedQueue CAS 高并发队列(无阻塞)
ConcurrentHashMap 实战(单词计数)
// 高并发下单词计数(线程安全)
ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>();
List<String> words = Arrays.asList("a", "b", "a", "c", "b", "a");

// 多线程计数
for (String word : words) {
    // 不存在则创建LongAdder,存在则自增
    map.computeIfAbsent(word, k -> new LongAdder()).increment();
}

// 输出结果
map.forEach((k, v) -> log.debug("{}: {}", k, v.sum()));
BlockingQueue 实战(生产者 - 消费者)
// 阻塞队列(容量3)
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

// 生产者线程
new Thread(() -> {
    try {
        queue.put("任务1");
        queue.put("任务2");
        queue.put("任务3");
        queue.put("任务4"); // 队列满,阻塞
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "生产者").start();

// 消费者线程
new Thread(() -> {
    try {
        Thread.sleep(1000);
        log.debug("消费:{}", queue.take());
        Thread.sleep(1000);
        log.debug("消费:{}", queue.take());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "消费者").start();

7.3 同步工具类

1. Semaphore(信号量)
  • 作用:限制同时访问共享资源的线程数(如限流)。
  • 实战:3 个许可,10 个线程竞争。
Semaphore semaphore = new Semaphore(3); // 3个许可
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire(); // 获取许可
            log.debug("运行中");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release(); // 释放许可
        }
    }, "t" + i).start();
}
2. CountDownLatch(倒计时器)
  • 作用:等待多个线程完成后再执行主线程。
  • 实战:等待 3 个线程完成。
CountDownLatch latch = new CountDownLatch(3);

// 3个工作线程
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        log.debug("线程执行完毕");
        latch.countDown(); // 计数减1
    }, "t" + i).start();
}

latch.await(); // 等待计数为0
log.debug("所有线程执行完毕,主线程继续");
3. CyclicBarrier(循环栅栏)
  • 作用:等待线程达到计数后,同时继续执行(可重用)。
  • 实战:3 个线程到达后,执行回调。
// 3个线程到达后,执行回调
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    log.debug("所有线程到达,执行回调");
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        log.debug("线程到达栅栏");
        try {
            barrier.await(); // 等待其他线程
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        log.debug("线程继续执行");
    }, "t" + i).start();
}

7.4 Fork/Join 框架(分治并行计算)

核心思想

将大任务拆分为多个小任务并行执行,最后汇总结果(适合 CPU 密集型任务)。

核心 API
  • RecursiveTask:有返回值的任务。
  • RecursiveAction:无返回值的任务。
  • ForkJoinPool:执行 Fork/Join 任务的线程池。
实战:1~n 求和
// 有返回值的分治任务
class SumTask extends RecursiveTask<Long> {
    private long start;
    private long end;

    // 阈值(小于阈值直接计算,不拆分)
    private static final long THRESHOLD = 1000;

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

    @Override
    protected Long compute() {
        long length = end - start + 1;
        // 小于阈值,直接计算
        if (length <= THRESHOLD) {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }
        // 拆分任务
        long mid = (start + end) / 2;
        SumTask leftTask = new SumTask(start, mid);
        SumTask rightTask = new SumTask(mid + 1, end);
        // 并行执行子任务
        leftTask.fork();
        rightTask.fork();
        // 汇总结果
        return leftTask.join() + rightTask.join();
    }
}

// 执行任务
public static void main(String[] args) {
    ForkJoinPool pool = new ForkJoinPool();
    SumTask task = new SumTask(1, 100000000);
    Long result = pool.invoke(task);
    log.debug("1~100000000求和结果:{}", result);
    pool.shutdown();
}

7.5 CompletableFuture(异步编程)

核心作用

简化异步编程,无需手动创建线程,支持链式调用、结果回调、异常处理。

核心 API
  • runAsync:无返回值的异步任务。
  • supplyAsync:有返回值的异步任务。
  • thenApply:任务完成后处理结果(有返回值)。
  • thenAccept:任务完成后消费结果(无返回值)。
  • exceptionally:异常处理。
实战:异步查询商品信息
// 线程池(推荐自定义,避免默认线程池耗尽)
private static final ExecutorService executor = Executors.newFixedThreadPool(5);

public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 异步查询商品基本信息(有返回值)
    CompletableFuture<Product> productFuture = CompletableFuture.supplyAsync(() -> {
        log.debug("查询商品基本信息");
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new Product(1L, "手机", 3999.0);
    }, executor);

    // 异步查询商品库存(有返回值)
    CompletableFuture<Integer> stockFuture = CompletableFuture.supplyAsync(() -> {
        log.debug("查询商品库存");
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 100;
    }, executor);

    // 合并两个异步结果
    CompletableFuture<Void> resultFuture = productFuture.thenCombine(stockFuture, (product, stock) -> {
        log.debug("商品:{},库存:{}", product.getName(), stock);
        return null;
    });

    // 等待结果完成
    resultFuture.get();
    executor.shutdown();
}

@Data
static class Product {
    private Long id;
    private String name;
    private Double price;

    public Product(Long id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

八、并发模式实战

8.1 保护性暂停模式(Guarded Suspension)

核心思想

一个线程等待另一个线程的结果(如主线程等待子线程处理结果),通过对象锁实现等待 - 通知。

实战实现
// 结果容器
class GuardedObject {
    private Object response;
    private final Object lock = new Object();

    // 获取结果(阻塞等待)
    public Object get() {
        synchronized (lock) {
            while (response == null) {
                try {
                    lock.wait(); // 等待结果
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    // 设置结果(唤醒等待线程)
    public void complete(Object response) {
        synchronized (lock) {
            this.response = response;
            lock.notifyAll(); // 唤醒等待线程
        }
    }
}

// 实战使用
public static void main(String[] args) {
    GuardedObject guardedObject = new GuardedObject();

    // 子线程执行任务,设置结果
    new Thread(() -> {
        log.debug("子线程执行任务");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        guardedObject.complete("任务结果");
    }, "子线程").start();

    // 主线程等待结果
    log.debug("主线程等待结果");
    Object result = guardedObject.get();
    log.debug("主线程获取结果:{}", result);
}

8.2 生产者 - 消费者模式

核心思想

生产者线程生产数据,消费者线程消费数据,通过阻塞队列缓冲,解耦生产者和消费者。

实战实现(基于 BlockingQueue)
// 产品类
@Data
class Product {
    private Long id;
    private String name;

    public Product(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

public static void main(String[] args) {
    // 阻塞队列(缓冲)
    BlockingQueue<Product> queue = new ArrayBlockingQueue<>(10);

    // 生产者线程(3个)
    for (int i = 0; i < 3; i++) {
        int finalI = i;
        new Thread(() -> {
            try {
                for (int j = 0; j < 5; j++) {
                    Product product = new Product((long) (finalI * 10 + j), "商品" + (finalI * 10 + j));
                    queue.put(product);
                    log.debug("生产者{}生产商品:{}", finalI, product.getName());
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "生产者" + i).start();
    }

    // 消费者线程(2个)
    for (int i = 0; i < 2; i++) {
        new Thread(() -> {
            try {
                while (true) {
                    Product product = queue.take();
                    log.debug("消费者{}消费商品:{}", Thread.currentThread().getName(), product.getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "消费者" + i).start();
    }
}

8.3 两阶段终止模式(Two-Phase Termination)

核心思想

优雅终止线程(先通知,再等待线程完成收尾工作),避免线程强制中断导致的数据不一致。

实战实现(基于 interrupt)
class WorkerThread extends Thread {
    private volatile boolean stop = false;

    @Override
    public void run() {
        while (true) {
            if (stop) {
                log.debug("线程准备终止,执行收尾工作");
                // 执行收尾工作(如释放资源)
                break;
            }
            try {
                Thread.sleep(1000); // 模拟工作
                log.debug("线程执行中...");
            } catch (InterruptedException e) {
                // 捕获interrupt,设置stop标志
                stop = true;
            }
        }
    }

    // 终止线程(两阶段:通知+等待)
    public void terminate() {
        stop = true;
        this.interrupt(); // 唤醒阻塞的线程
    }
}

public static void main(String[] args) throws InterruptedException {
    WorkerThread worker = new WorkerThread();
    worker.start();

    // 主线程3秒后终止worker线程
    Thread.sleep(3000);
    log.debug("主线程通知worker线程终止");
    worker.terminate();
    worker.join(); // 等待worker线程完成收尾
    log.debug("worker线程已终止");
}

九、实战习题解析

9.1 卖票问题(线程安全)

问题描述

多个线程同时卖票,避免超卖和重复卖票。

解决方案(synchronized)
class TicketWindow {
    private int count;

    public TicketWindow(int count) {
        this.count = count;
    }

    // 卖票(加锁保证线程安全)
    public synchronized int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        }
        return 0;
    }

    public int getCount() {
        return count;
    }
}

public static void main(String[] args) throws InterruptedException {
    TicketWindow window = new TicketWindow(2000);
    List<Thread> threads = new ArrayList<>(); //threads是给主线程使用的,只有一个线程,不用使用线程安全的集合
    List<Integer> sellCount = new Vector<>(); // Vector线程安全,sellCount是交给不同的买票线程使用的,所以要确保线程安全

    for (int i = 0; i < 2000; i++) {
        Thread t = new Thread(() -> {
            int count = window.sell(randomAmount());//只要确保sell方法是线程安全的,整个买票操作就是线程安全的了,因为window和sellCount是不同的共享资源
            sellCount.add(count);//add方法是线程安全的
        });
        threads.add(t);
        t.start();
    }

    for (Thread t : threads) {
        t.join();
    }

    log.debug("卖出票数:{}", sellCount.stream().mapToInt(Integer::intValue).sum());
    log.debug("剩余票数:{}", window.getCount());
}

// 随机1~5张票
private static int randomAmount() {
    return new Random().nextInt(5) + 1;
}

9.2 转账问题(线程安全)

问题描述

两个账户之间转账,避免余额不足和并发安全问题。

解决方案(锁顺序 + synchronized)
class Account {
    private int money;
    private final Object lock = new Object();

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    // 转账(按账户hashCode顺序加锁,避免死锁)
    public boolean transfer(Account target, int amount) {
        Account left = this;
        Account right = target;
        // 按hashCode顺序确定加锁顺序
        if (this.hashCode() > target.hashCode()) {
            left = target;
            right = this;
        }

        synchronized (left) {
            synchronized (right) {
                if (this.money >= amount) {
                    this.money -= amount;
                    target.money += amount;
                    return true;
                }
                return false;
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Account a = new Account(1000);
    Account b = new Account(1000);
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 1000; i++) {
            a.transfer(b, randomAmount());
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 1000; i++) {
            b.transfer(a, randomAmount());
        }
    }, "t2");

    t1.start();
    t2.start();
    t1.join();
    t2.join();

    log.debug("总金额:{}", a.getMoney() + b.getMoney());
}

// 随机1~100元
private static int randomAmount() {
    return new Random().nextInt(100) + 1;
}
解决方案(加类锁)

性能差。

// 账户
class Account {
    private int money;

    public Account(int money) { this.money = money; }

    public int getMoney() { return money; }

    public void setMoney(int money) { this.money = money; }

    // 转账
    public void transfer(Account target, int amount) {
        synchronized(Account.class) {
            if (this.money >= amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}

9.3 烧水泡茶(统筹并发)

需求

用两个线程模拟烧水泡茶流程,优化时间(洗水壶 1min → 烧开水 15min → 泡茶;烧开水期间洗茶壶 1min、洗茶杯 2min、拿茶叶 1min)。

实现代码
public class MakeTea {
    // 锁对象
    static final Object lock = new Object();
    // 标记:水是否烧开
    static boolean isBoiled = false;

    public static void main(String[] args) {
        // 线程1:洗水壶→烧开水
        new Thread(() -> {
            synchronized (lock) {
                log.debug("洗水壶(1min)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("烧开水(15min)");
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                isBoiled = true;
                lock.notify(); // 唤醒线程2
            }
        }, "线程A").start();

        // 线程2:等待水烧开→洗茶壶→洗茶杯→拿茶叶→泡茶
        new Thread(() -> {
            synchronized (lock) {
                while (!isBoiled) {
                    try {
                        log.debug("等待水烧开...");
                        lock.wait(); // 等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("洗茶壶(1min)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("洗茶杯(2min)");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("拿茶叶(1min)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("泡茶");
            }
        }, "线程B").start();
    }
}

十、总结与拓展

核心知识点梳理

  1. 基础层:进程 / 线程、并发 / 并行、线程状态与 API。
  2. 锁机制:synchronized(对象锁)、Lock(可中断 / 超时 / 公平锁)。
  3. 内存模型:JMM 三大特性(原子性 / 可见性 / 有序性)、volatile、happens-before。
  4. 无锁编程:CAS、原子类、ABA 问题解决。
  5. 工具层:线程池、并发集合、同步工具类(Semaphore/CountDownLatch 等)。
  6. 高级特性:Fork/Join 分治、CompletableFuture 异步编程。
  7. 并发模式:保护性暂停、生产者消费者、两阶段终止。

实际应用建议

  1. 优先使用 JUC 工具类(如线程池、ConcurrentHashMap),避免手动创建线程。
  2. 简单线程安全场景用synchronized,复杂场景用Lock(如需要超时 / 中断)。
  3. 读多写少用CopyOnWriteArrayList,高并发键值对用ConcurrentHashMap
  4. 避免死锁:按固定顺序加锁、超时获取锁。
  5. 异步编程优先用CompletableFuture,配合自定义线程池提高性能。

拓展学习方向

  1. 分布式并发:分布式锁(Redis/ZooKeeper)、分布式计数器、分布式事务。
  2. 反应式编程:Project Reactor、Spring WebFlux、响应式流规范。
  3. 性能优化:CPU 缓存优化、伪共享、锁消除 / 锁粗化。
  4. 开源框架并发:Tomcat 线程池、Netty 并发模型、Spring 事务并发。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐