JUC 并发编程从入门到精通(超详细笔记 + 实战案例)

一、并发基础核心概念

1.1 进程与线程

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

1.2 并行与并发

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

2.3 并发的核心应用场景

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

二、Java 线程核心操作

2.1 线程创建的三种方式

方式 1:直接继承 Thread

java

运行

java 复制代码
// 推荐指定线程名,便于调试
Thread t1 = new Thread("t1") {
    @Override
    public void run() {
        log.debug("线程t1执行"); // 输出:19:19:00 [t1] c.ThreadStarter - 线程t1执行
    }
};
t1.start(); // 启动线程(不可重复调用)
方式 2:Runnable 配合 Thread(解耦线程与任务)
java 复制代码
// 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 处理有返回值的任务
java 复制代码
// 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 支持返回值 需处理异常 需获取任务结果(如并行计算)

3.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 的区别
java 复制代码
// 错误:直接调用run(主线程执行)
Thread t1 = new Thread("t1") {
    @Override
    public void run() {
        log.debug(Thread.currentThread().getName()); // 输出main
    }
};
t1.run();

// 正确:调用start(新线程执行)
t1.start(); // 输出t1
  1. join 等待线程完成
java 复制代码
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)
}
  1. interrupt 打断线程
java 复制代码
// 打断阻塞线程(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

3.3 线程状态(Java API 层面)

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

3.4 主线程与守护线程

核心定义
  • 守护线程:依赖非守护线程,当所有非守护线程结束,守护线程强制终止(无需执行完)。
  • 非守护线程:默认线程类型(如主线程),需等待自身执行完毕。
实战示例:
java 复制代码
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)

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

问题本质

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

经典案例:静态变量自增自减:
java 复制代码
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:将结果写回主存。

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

4.2 synchronized 解决方案(对象锁)

核心原理

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

  1. 代码块形式(推荐,锁粒度可控)
java 复制代码
static final Object lock = new Object(); // 锁对象(任意对象均可)
static int counter = 0;

// 临界区代码
synchronized (lock) {
    counter++; // 原子操作
}
  1. 成员方法形式(锁当前对象 this)
java 复制代码
public synchronized void increment() {
    counter++;
}
  1. 静态方法形式(锁类对象 Class)
java 复制代码
public static synchronized void decrement() {
    counter--;
}
解决线程安全问题
java 复制代码
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自动释放锁(异常或代码执行完毕时)。

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

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

4.4 Lock 接口(synchronized 增强版)

Lock 与 synchronized 对比
特性 synchronized Lock(ReentrantLock)
可中断 是(lockInterruptibly()
超时获取 是(tryLock(long)
公平锁 是(构造函数指定true
多条件变量 是(newCondition()
手动释放 否(自动) 是(必须在 finally 中unlock()
ReentrantLock 核心用法
java 复制代码
// 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(); // 释放锁
}
可中断锁实战
java 复制代码
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();

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

1. Object 的 wait/notify(synchronized 配套)
java 复制代码
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 配套,多条件精准唤醒)
java 复制代码
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();
}

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

1. 死锁(最常见)
死锁条件
  • 资源互斥
  • 持有并等待
  • 不可剥夺
  • 循环等待
死锁示例
java 复制代码
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)

5.1 JMM 核心目标

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

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

5.2 可见性问题

问题示例(退不出的循环)
java 复制代码
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 关键字
java 复制代码
static volatile boolean run = true; // 加volatile
volatile 核心作用
  • 强制线程读写变量时直接操作主存(不经过 CPU 缓存)。
  • 禁止指令重排(保证有序性)。
注意:volatile 不保证原子性
java 复制代码
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(原子性问题)
}

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

指令重排定义

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

多线程下的问题示例
java 复制代码
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(保证原子性的同时保证有序性)。

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

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

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

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

6.1 CAS 核心原理

定义

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

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

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

CAS 实战(原子类 AtomicInteger)
java 复制代码
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();
    }
}

6.2 原子类家族

1. 基本类型原子类
  • AtomicInteger/AtomicLong/AtomicBoolean:解决基本类型的原子操作。
  • 核心方法:getAndIncrement()(i++)、incrementAndGet()(++i)、compareAndSet()
2. 引用类型原子类
  • AtomicReference:原子操作引用类型。
  • AtomicStampedReference:解决 ABA 问题(加版本号)。
  • AtomicMarkableReference:解决 ABA 问题(加标记,不关心修改次数)。
3. ABA 问题及解决
ABA 问题示例
java 复制代码
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
java 复制代码
// 初始化:值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
}

6.3 原子累加器(LongAdder)

为什么比 AtomicLong 快?
  • AtomicLong:高并发下 CAS 重试频繁,效率低。
  • LongAdder:分段累加(多个 Cell 单元),减少 CAS 竞争,最后汇总结果。
实战对比
java 复制代码
// 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();
}

七、共享模型之不可变

7.1 不可变类定义

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

7.2 不可变类的线程安全

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

  • StringIntegerLong等包装类。
  • Java 8 的DateTimeFormatter(线程安全,替代SimpleDateFormat)。
实战:日期格式化(线程安全)
java 复制代码
// 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();
}

7.3 无状态设计

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

八、JUC 核心工具实战

8.1 线程池(ThreadPoolExecutor)

线程池核心作用
  • 复用线程(避免线程创建 / 销毁的开销)。
  • 控制线程数量(防止资源耗尽)。
  • 管理任务队列(缓冲任务)。
核心参数
java 复制代码
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 放弃队列中最早的任务
实战:自定义线程池
java 复制代码
// 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(); // 立即关闭,中断正在执行的任务

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

常用并发集合对比
集合类型 线程安全实现 适用场景
ConcurrentHashMap CAS + 分段锁 高并发键值对存储
CopyOnWriteArrayList 写入时拷贝数组 读多写少(如配置列表)
BlockingQueue 锁 + 条件变量 生产者 - 消费者模型
ConcurrentLinkedQueue CAS 高并发队列(无阻塞)
ConcurrentHashMap 实战(单词计数)
java 复制代码
// 高并发下单词计数(线程安全)
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 实战(生产者 - 消费者)
java 复制代码
// 阻塞队列(容量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();

8.3 同步工具类

1. Semaphore(信号量)
  • 作用:限制同时访问共享资源的线程数(如限流)。
  • 实战:3 个许可,10 个线程竞争。
java 复制代码
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 个线程完成。
java 复制代码
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 个线程到达后,执行回调。
java 复制代码
// 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();
}

九、实战案例:烧水泡茶(统筹并发)

需求

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

实现代码

java 复制代码
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();
    }
}

执行结果(总耗时≈16min)

java 复制代码
10:00:00 [线程A] c.MakeTea - 洗水壶(1min)
10:00:01 [线程A] c.MakeTea - 烧开水(15min)
10:00:01 [线程B] c.MakeTea - 等待水烧开...
10:00:16 [线程B] c.MakeTea - 洗茶壶(1min)
10:00:17 [线程B] c.MakeTea - 洗茶杯(2min)
10:00:19 [线程B] c.MakeTea - 拿茶叶(1min)
10:00:20 [线程B] c.MakeTea - 泡茶

十、总结与拓展

核心知识点梳理

  1. 基础层:进程 / 线程、并发 / 并行、线程状态与 API。
  2. 锁机制:synchronized(对象锁)、Lock(可中断 / 超时 / 公平锁)。
  3. 内存模型:JMM 三大特性(原子性 / 可见性 / 有序性)、volatile、happens-before。
  4. 无锁编程:CAS、原子类、ABA 问题解决。
  5. 工具层:线程池、并发集合、同步工具类(Semaphore/CountDownLatch 等)。

实际应用建议

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

拓展学习方向

  1. 异步编程:CompletableFuture、Guava ListenableFuture。
  2. 反应式编程:Project Reactor、Spring WebFlux。
  3. 分布式并发:分布式锁(Redis/ZooKeeper)、分布式计数器。
  4. 性能优化:无锁编程、CAS 优化、CPU 缓存优化。
相关推荐
带刺的坐椅19 小时前
用 10 行 Java8 代码,开发一个自己的 ClaudeCodeCLI?你信吗?
java·ai·llm·agent·solon·mcp·claudecode·skills
Nebula_g19 小时前
线程进阶: 无人机自动防空平台开发教程(更新)
java·开发语言·数据结构·学习·算法·无人机
HAPPY酷20 小时前
构造与析构:C++ 中对象的温柔生灭
java·jvm·c++
乔江seven20 小时前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask
lang2015092820 小时前
Java JSR 250核心注解全解析
java·开发语言
czhc114007566320 小时前
协议 25
java·开发语言·算法
逆光的July20 小时前
如何解决超卖问题
java
落花流水 丶20 小时前
Java 集合框架完全指南
java
lang2015092820 小时前
Java WebSocket API:JSR-356详解
java·python·websocket
这周也會开心20 小时前
Redis与MySQL回写中的数据类型存储设计
数据库·redis·mysql