编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
        • 13.1 线程基础
          • 13.1.1 线程介绍
          • 13.1.2 创建线程-继承Thread
          • 13.1.3 创建线程-实现Runnable
          • 13.1.4 创建线程-实现Callable
          • 13.1.5 线程生命周期
          • 13.1.6 综合案例:多线程创建对比
          • 13.1.7 训练题
        • 13.2 线程控制
          • 13.2.1 线程休眠sleep
          • 13.2.2 线程等待join
          • 13.2.3 线程让步yield
          • 13.2.4 守护线程
          • 13.2.5 综合案例:线程控制实验
          • 13.2.6 线程控制训练题
        • 13.3 线程安全
          • 13.3.1 数据竞争问题
          • 13.3.2 synchronized关键字
          • 13.3.3 synchronized方法和代码块
          • 13.3.4 综合案例:线程安全的计数器
          • 13.3.5 线程安全训练题
        • 13.4 Lock锁
          • 13.4.1 ReentrantLock
          • 13.4.2 读写锁ReadWriteLock
          • 13.4.3 综合案例:读写锁缓存
          • 13.4.4 Lock锁训练题
        • 13.5 线程通信
          • 13.5.1 wait和notify
          • 13.5.2 生产者消费者模型
          • 13.5.3 综合案例:生产者消费者队列
          • 13.5.4 训练题
        • 13.6 线程池
          • 13.6.1 为什么需要线程池
          • 13.6.2 创建线程池
          • 13.6.3 线程池参数
          • 13.6.4 综合案例:任务调度器
          • 13.6.5 线程池训练题
        • 13.7 volatile和原子类
          • 13.7.1 综合案例:并发计数器对比
          • 13.7.2 训练题
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

线程和锁

# 13.线程和锁

# 目录介绍

  • 13.1 线程基础
    • 13.1.1 线程介绍
    • 13.1.2 创建线程-继承Thread
    • 13.1.3 创建线程-实现Runnable
    • 13.1.4 创建线程-实现Callable
    • 13.1.5 线程生命周期
    • 13.1.6 综合案例:多线程创建对比
    • 13.1.7 训练题
  • 13.2 线程控制
    • 13.2.1 线程休眠sleep
    • 13.2.2 线程等待join
    • 13.2.3 线程让步yield
    • 13.2.4 守护线程
    • 13.2.5 综合案例:线程控制实验
    • 13.2.6 线程控制训练题
  • 13.3 线程安全
    • 13.3.1 数据竞争问题
    • 13.3.2 synchronized关键字
    • 13.3.3 synchronized方法和代码块
    • 13.3.4 综合案例:线程安全的计数器
    • 13.3.5 线程安全训练题
  • 13.4 Lock锁
    • 13.4.1 ReentrantLock
    • 13.4.2 读写锁ReadWriteLock
    • 13.4.3 综合案例:读写锁缓存
    • 13.4.4 Lock锁训练题
  • 13.5 线程通信
    • 13.5.1 wait和notify
    • 13.5.2 生产者消费者模型
    • 13.5.3 综合案例:生产者消费者队列
    • 13.5.4 训练题
  • 13.6 线程池
    • 13.6.1 为什么需要线程池
    • 13.6.2 创建线程池
    • 13.6.3 线程池参数
    • 13.6.4 综合案例:任务调度器
    • 13.6.5 线程池训练题
  • 13.7 volatile和原子类
    • 13.7.1 综合案例:并发计数器对比
    • 13.7.2 训练题

# 13.1 线程基础

# 13.1.1 线程介绍

Java 从语言级别支持多线程编程。一个 Java 程序至少有一个线程——main 线程。

对比 C++:C++11 才通过 std::thread 引入标准库线程支持,Java 从 1.0 版本就内置了线程支持。

# 13.1.2 创建线程-继承Thread

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ": " + i);
        }
    }
}

MyThread t = new MyThread();
t.start();  // 启动线程(不是调用 run)
1
2
3
4
5
6
7
8
9
10
11

# 13.1.3 创建线程-实现Runnable

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

Thread t = new Thread(new MyRunnable());
t.start();

// Lambda 简写
Thread t2 = new Thread(() -> {
    System.out.println("Lambda 线程执行");
});
t2.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 13.1.4 创建线程-实现Callable

Callable 可以有返回值和抛出异常:

import java.util.concurrent.*;

Callable<Integer> task = () -> {
    int sum = 0;
    for (int i = 1; i <= 100; i++) sum += i;
    return sum;
};

FutureTask<Integer> future = new FutureTask<>(task);
new Thread(future).start();
System.out.println("结果:" + future.get());  // 5050
1
2
3
4
5
6
7
8
9
10
11

# 13.1.5 线程生命周期

新建(New) → start() → 就绪(Runnable) → 获得CPU → 运行(Running)
                                              ↓
                            阻塞(Blocked/Waiting/Timed_Waiting)
                                              ↓
                                        终止(Terminated)
1
2
3
4
5

# 13.1.6 综合案例:多线程创建对比

本案例对比三种创建线程方式的区别和适用场景。

import java.util.concurrent.*;

/**
 * 多线程创建对比 —— 线程基础综合案例
 * 知识点:Thread继承、Runnable接口、Callable+Future、线程生命周期
 */
public class ThreadCreateDemo {
    // 方式1:继承 Thread
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("  [Thread] " + getName() + " 运行中");
        }
    }

    // 方式2:实现 Runnable
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("  [Runnable] " + Thread.currentThread().getName() + " 运行中");
        }
    }

    // 方式3:实现 Callable(有返回值)
    static class MyCallable implements Callable<Integer> {
        private int n;
        MyCallable(int n) { this.n = n; }

        @Override
        public Integer call() {
            int sum = 0;
            for (int i = 1; i <= n; i++) sum += i;
            System.out.println("  [Callable] 计算 1+" + "...+" + n + " = " + sum);
            return sum;
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("===== 多线程创建对比 =====\n");

        // 方式1
        System.out.println("--- 继承Thread ---");
        new MyThread().start();

        // 方式2
        System.out.println("--- 实现Runnable ---");
        new Thread(new MyRunnable()).start();

        // 方式2 Lambda简写
        new Thread(() -> System.out.println("  [Lambda] 运行中")).start();

        Thread.sleep(100);

        // 方式3
        System.out.println("\n--- 实现Callable ---");
        FutureTask<Integer> ft = new FutureTask<>(new MyCallable(100));
        new Thread(ft).start();
        System.out.println("  结果: " + ft.get());

        // 对比总结
        System.out.println("\n--- 对比 ---");
        System.out.println("  Thread   : 简单但不能再继承其他类");
        System.out.println("  Runnable : 灵活,推荐用Lambda");
        System.out.println("  Callable : 有返回值,可抛异常");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# 13.1.7 训练题

  1. 实践题:分别使用继承 Thread、实现 Runnable、实现 Callable 三种方式创建线程,每个线程打印 1~10。对比三种方式的代码量和灵活性。

  2. 代码题:以下代码的输出是什么?start() 和 run() 有什么区别?

    Thread t = new Thread(() -> System.out.println("子线程:" + Thread.currentThread().getName()));
    t.run();    // 直接调用 run
    t.start();  // 启动线程
    
    1
    2
    3
  3. 思考题:Java 线程有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),请画出它们之间的转换关系,并说明哪些方法/操作会触发状态转换。

# 13.2 线程控制

# 13.2.1 线程休眠sleep

Thread.sleep(1000);  // 当前线程休眠1秒
1

# 13.2.2 线程等待join

Thread t = new Thread(() -> {
    System.out.println("子线程执行");
});
t.start();
t.join();  // 主线程等待 t 执行完毕
System.out.println("子线程执行完了");
1
2
3
4
5
6

# 13.2.3 线程让步yield

Thread.yield();  // 让出CPU时间片,让其他线程执行
1

# 13.2.4 守护线程

Thread daemon = new Thread(() -> {
    while (true) {
        System.out.println("守护线程运行中...");
    }
});
daemon.setDaemon(true);  // 设为守护线程
daemon.start();
// 主线程结束后,守护线程自动终止
1
2
3
4
5
6
7
8

# 13.2.5 综合案例:线程控制实验

本案例演示 sleep、join、yield 和守护线程的使用。

/**
 * 线程控制实验 —— 线程控制综合案例
 * 知识点:sleep、join、yield、守护线程
 */
public class ThreadControlDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("===== 线程控制实验 =====\n");

        // 1. join:等待子线程完成
        System.out.println("--- join ---");
        Thread worker = new Thread(() -> {
            try {
                System.out.println("  工作线程开始");
                Thread.sleep(500);
                System.out.println("  工作线程完成");
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        });
        worker.start();
        worker.join();  // 主线程等待 worker 完成
        System.out.println("  主线程继续(join 后)");

        // 2. 守护线程
        System.out.println("\n--- 守护线程 ---");
        Thread daemon = new Thread(() -> {
            while (true) {
                System.out.println("  守护线程运行中...");
                try { Thread.sleep(200); } catch (InterruptedException e) { break; }
            }
        });
        daemon.setDaemon(true);  // 设为守护线程
        daemon.start();
        Thread.sleep(500);  // 主线程等一会
        System.out.println("  主线程即将结束,守护线程自动终止");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 13.2.6 线程控制训练题

训练1:创建 3 个线程,让它们按顺序执行(线程1执行完→线程2执行→线程3执行)。使用 join() 实现。

训练2:编写一个程序,主线程创建一个守护线程(每隔1秒打印当前时间),主线程 sleep(5000) 后结束。观察守护线程是否会在主线程结束后继续运行。

思考:sleep() 和 wait() 都能让线程暂停,但有本质区别。请从"是否释放锁"和"唤醒方式"两个角度解释。

# 13.3 线程安全

# 13.3.1 数据竞争问题

public class Counter {
    private int count = 0;

    public void increment() {
        count++;  // 非原子操作,多线程下会出问题
    }

    public int getCount() {
        return count;
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 13.3.2 synchronized关键字

public class Counter {
    private int count = 0;

    // synchronized 方法
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

synchronized 的底层原理:每个 Java 对象的对象头中都包含一个 Mark Word,用于存储锁信息。synchronized 在字节码中对应 monitorenter/monitorexit 指令(同步方法则通过 ACC_SYNCHRONIZED 标志实现)。JDK 6+ 对锁做了大量优化,引入了锁升级机制:无锁 → 偏向锁(记录持有线程ID,无竞争时零开销)→ 轻量级锁(CAS 自旋,短期竞争时避免线程阻塞)→ 重量级锁(OS 互斥量 Mutex,线程进入阻塞状态)。锁只能升级不能降级。这就是为什么在低竞争场景下 synchronized 的性能已经很好。

# 13.3.3 synchronized方法和代码块

public class Account {
    private double balance;
    private final Object lock = new Object();

    // 同步方法
    public synchronized void deposit(double amount) {
        balance += amount;
    }

    // 同步代码块(更细粒度的控制)
    public void withdraw(double amount) {
        synchronized (lock) {
            if (balance >= amount) {
                balance -= amount;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

对比 C++:C++ 使用 std::mutex 和 std::lock_guard,Java 用 synchronized 关键字更加简洁。

# 13.3.4 综合案例:线程安全的计数器

本案例演示数据竞争问题及 synchronized 的三种使用方式。

/**
 * 线程安全的计数器 —— 线程安全综合案例
 * 知识点:数据竞争、synchronized方法、synchronized代码块、类锁
 */
public class SafeCounter {
    private int count = 0;
    private static int staticCount = 0;

    // 同步方法(锁this)
    public synchronized void increment() {
        count++;
    }

    // 同步代码块(锁指定对象)
    private final Object lock = new Object();
    public void incrementWithBlock() {
        synchronized (lock) {
            count++;
        }
    }

    // 静态同步方法(锁Class对象)
    public static synchronized void staticIncrement() {
        staticCount++;
    }

    public int getCount() { return count; }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("===== 线程安全计数器 =====\n");

        SafeCounter counter = new SafeCounter();
        int threadCount = 10, perThread = 10000;

        // 使用 synchronized 方法
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < perThread; j++) counter.increment();
            });
            threads[i].start();
        }
        for (Thread t : threads) t.join();

        int expected = threadCount * perThread;
        System.out.printf("synchronized方法: %d (期望%d) %s%n",
                counter.getCount(), expected,
                counter.getCount() == expected ? "✓" : "✗");

        System.out.println("\nsynchronized 三种形式:");
        System.out.println("  1. synchronized方法  : 锁this对象");
        System.out.println("  2. synchronized代码块: 锁指定对象(更灵活)");
        System.out.println("  3. static synchronized: 锁Class对象(类级别)");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 13.3.5 线程安全训练题

训练1:编写一个多线程"抢票系统"——有 100 张票,3 个窗口(线程)同时售票。要求:(a) 不使用同步,观察超卖现象;(b) 使用 synchronized 解决;(c) 使用 AtomicInteger 解决。

训练2:以下代码是否线程安全?如果不安全,如何修改?

class BankAccount {
    private int balance = 1000;
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    public int getBalance() {  // 注意:没有 synchronized
        return balance;
    }
}
1
2
3
4
5
6
7
8
9
10
11

疑惑:synchronized 经常被说"性能差",那到底差多少?

答疑:这是一个过时的认知。JDK 6 之前 synchronized 确实每次都走重量级锁(OS 互斥量),涉及用户态→内核态切换。但 JDK 6+ 引入了锁升级机制后,在低竞争场景下性能已经很好:

论证——锁升级过程:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  │       │         │          │
  │     零开销    CAS自旋    OS Mutex
  │   记录线程ID  短期竞争    线程阻塞
1
2
3
4
  • 偏向锁:第一个线程获取锁时,在对象头的 Mark Word 中记录线程 ID。后续同一线程再次进入时,只需检查 ID 一致即可,几乎零开销。
  • 轻量级锁:第二个线程竞争时,升级为轻量级锁。使用 CAS 操作在栈帧中创建 Lock Record。
  • 重量级锁:CAS 自旋失败次数超阈值后,升级为重量级锁,使用 OS 互斥量。

结果展示:在单线程或低竞争场景下,synchronized 与无锁代码性能差距仅为几纳秒级别。实际业务中不必刻意避免 synchronized。

# 13.4 Lock锁

# 13.4.1 ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private double balance;
    private final ReentrantLock lock = new ReentrantLock();

    public void deposit(double amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();  // 必须在 finally 中释放锁
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 13.4.2 读写锁ReadWriteLock

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Account {
    private double balance;
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public double getBalance() {
        rwLock.readLock().lock();  // 读锁(多线程可同时读)
        try {
            return balance;
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void deposit(double amount) {
        rwLock.writeLock().lock();  // 写锁(独占)
        try {
            balance += amount;
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 13.4.3 综合案例:读写锁缓存

本案例综合运用 ReentrantLock 和 ReadWriteLock 实现线程安全的缓存。

import java.util.*;
import java.util.concurrent.locks.*;

/**
 * 读写锁缓存 —— Lock综合案例
 * 知识点:ReentrantLock、ReadWriteLock、tryLock、读写分离
 */
public class ThreadSafeCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public V get(K key) {
        rwLock.readLock().lock();  // 读锁(多线程可同时读)
        try {
            return cache.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void put(K key, V value) {
        rwLock.writeLock().lock();  // 写锁(独占)
        try {
            cache.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    public int size() {
        rwLock.readLock().lock();
        try {
            return cache.size();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("===== 读写锁缓存 =====\n");
        ThreadSafeCache<String, Integer> cache = new ThreadSafeCache<>();

        // 写线程
        Thread writer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                cache.put("key" + i, i * 10);
                System.out.println("  写入: key" + i + " = " + (i * 10));
            }
        });

        // 读线程
        Thread reader = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                Integer val = cache.get("key" + i);
                System.out.println("  读取: key" + i + " = " + val);
            }
        });

        writer.start();
        writer.join();
        reader.start();
        reader.join();

        System.out.println("\n缓存大小: " + cache.size());
        System.out.println("读写锁优势: 多线程可同时读,只有写时才独占");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 13.4.4 Lock锁训练题

训练1:使用 ReentrantLock 实现一个线程安全的银行账户,支持存款、取款和查询余额。用 tryLock 实现非阻塞的转账操作。

训练2:synchronized 和 ReentrantLock 的区别是什么?各自适合什么场景?

思考:ReadWriteLock 中,如果有线程持有读锁,其他线程能获取写锁吗?如果有线程持有写锁呢?这种设计的目的是什么?

# 13.5 线程通信

# 13.5.1 wait和notify

synchronized (lock) {
    while (条件不满足) {
        lock.wait();   // 释放锁并等待
    }
    // 执行操作
    lock.notifyAll();  // 唤醒其他等待的线程
}
1
2
3
4
5
6
7

# 13.5.2 生产者消费者模型

import java.util.LinkedList;
import java.util.Queue;

public class MessageQueue {
    private final Queue<String> queue = new LinkedList<>();
    private final int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void produce(String message) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();  // 队列满了,等待
        }
        queue.offer(message);
        System.out.println("生产:" + message);
        notifyAll();  // 通知消费者
    }

    public synchronized String consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();  // 队列空了,等待
        }
        String message = queue.poll();
        System.out.println("消费:" + message);
        notifyAll();  // 通知生产者
        return message;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 13.5.3 综合案例:生产者消费者队列

本案例用 wait/notify 实现一个有界阻塞队列,演示线程通信。

import java.util.LinkedList;

/**
 * 有界阻塞队列 —— 线程通信综合案例
 * 知识点:wait/notify、同步、生产者消费者模式
 */
public class BoundedQueue<T> {
    private final LinkedList<T> queue = new LinkedList<>();
    private final int capacity;

    public BoundedQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T item) throws InterruptedException {
        while (queue.size() == capacity) {
            System.out.println("    队列满,生产者等待...");
            wait();  // 满了就等待
        }
        queue.add(item);
        System.out.println("  生产: " + item + " (size=" + queue.size() + ")");
        notifyAll();  // 通知消费者
    }

    public synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) {
            System.out.println("    队列空,消费者等待...");
            wait();  // 空了就等待
        }
        T item = queue.removeFirst();
        System.out.println("  消费: " + item + " (size=" + queue.size() + ")");
        notifyAll();  // 通知生产者
        return item;
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("===== 有界阻塞队列 =====\n");
        BoundedQueue<Integer> bq = new BoundedQueue<>(3);

        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 6; i++) {
                    bq.put(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }, "Producer");

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 6; i++) {
                    bq.take();
                    Thread.sleep(300);  // 消费比生产慢
                }
            } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }, "Consumer");

        producer.start();
        consumer.start();
        producer.join();
        consumer.join();
        System.out.println("\n完成");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# 13.5.4 训练题

  1. 实践题:使用 wait/notify 实现两个线程交替打印 1~100(线程A打印奇数,线程B打印偶数)。

  2. 代码题:在生产者消费者模型中,为什么 wait() 要放在 while 循环中而不是 if 判断中?编写代码说明如果用 if 会出现什么问题(提示:虚假唤醒 spurious wakeup)。

  3. 思考题:除了 wait/notify,Java 还有哪些线程通信方式?(提示:Condition、BlockingQueue、CountDownLatch、Semaphore 等)请简要说明各自的适用场景。

# 13.6 线程池

# 13.6.1 为什么需要线程池

频繁创建和销毁线程开销大,线程池可以复用线程,控制并发数量。

# 13.6.2 创建线程池

import java.util.concurrent.*;

// 固定大小线程池
ExecutorService pool = Executors.newFixedThreadPool(3);

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

pool.shutdown();  // 关闭线程池
1
2
3
4
5
6
7
8
9
10
11
12
13

# 13.6.3 线程池参数

// 自定义线程池(推荐)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,                  // 核心线程数
    5,                  // 最大线程数
    60, TimeUnit.SECONDS, // 空闲线程存活时间
    new LinkedBlockingQueue<>(100),  // 任务队列
    new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略
);
1
2
3
4
5
6
7
8

线程池任务处理流程(疑惑—答疑—论证—结果):

疑惑:任务提交后,线程池到底是先创建线程还是先放队列?

答疑:既不是"先创建"也不是"先放队列",而是分三个阶段:

论证:

提交任务 submit(task)
    │
    ├─ 当前线程数 < corePoolSize?→ 是 → 创建核心线程执行
    │
    ├─ 任务队列未满?→ 是 → 放入队列等待
    │
    ├─ 当前线程数 < maximumPoolSize?→ 是 → 创建非核心线程执行
    │
    └─ 执行拒绝策略(AbortPolicy/CallerRunsPolicy/DiscardPolicy/DiscardOldestPolicy)
1
2
3
4
5
6
7
8
9

结果展示:假设 corePoolSize=2, maximumPoolSize=5, 队列容量=3,那么:

  • 第 1~2 个任务:创建核心线程
  • 第 3~5 个任务:放入队列
  • 第 6~8 个任务:创建非核心线程
  • 第 9+ 个任务:触发拒绝策略

四种拒绝策略:

策略 行为
AbortPolicy 抛出 RejectedExecutionException(默认)
CallerRunsPolicy 由提交任务的线程自己执行
DiscardPolicy 静默丢弃任务
DiscardOldestPolicy 丢弃队列中最旧的任务

# 13.6.4 综合案例:任务调度器

本案例综合运用线程池的创建、提交任务和参数配置。

import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 任务调度器 —— 线程池综合案例
 * 知识点:ThreadPoolExecutor参数、submit/Future、线程池选择
 */
public class TaskScheduler {
    public static void main(String[] args) throws Exception {
        System.out.println("===== 任务调度器 =====\n");

        // 自定义线程池(推荐方式)
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                       // 核心线程数
                4,                       // 最大线程数
                60, TimeUnit.SECONDS,    // 空闲线程存活时间
                new ArrayBlockingQueue<>(3), // 有界队列
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交 Callable 任务,获取 Future
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            Future<String> f = pool.submit(() -> {
                String name = Thread.currentThread().getName();
                System.out.printf("  任务%d 开始 [%s]%n", taskId, name);
                Thread.sleep(500);
                return "任务" + taskId + "完成";
            });
            futures.add(f);
        }

        // 获取结果
        System.out.println("\n--- 任务结果 ---");
        for (Future<String> f : futures) {
            System.out.println("  " + f.get());  // 阻塞等待
        }

        // 线程池状态
        System.out.println("\n--- 线程池状态 ---");
        System.out.println("  核心线程数: " + pool.getCorePoolSize());
        System.out.println("  最大线程数: " + pool.getMaximumPoolSize());
        System.out.println("  已完成任务: " + pool.getCompletedTaskCount());

        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("  线程池已关闭");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 13.6.5 线程池训练题

训练1:使用 ThreadPoolExecutor 创建一个线程池(核心2,最大4,队列容量3),提交 10 个任务,观察哪些任务被执行、哪些被拒绝。分别测试 4 种拒绝策略。

训练2:Executors.newFixedThreadPool() 和 Executors.newCachedThreadPool() 底层分别使用什么参数创建 ThreadPoolExecutor?为什么阿里巴巴编码规范禁止使用 Executors 创建线程池?

# 13.7 volatile和原子类

volatile 的底层原理:volatile 变量在字节码中没有特殊指令,但 JIT 编译后会在读写操作前后插入内存屏障(Memory Barrier)。写操作后插入 StoreStore + StoreLoad 屏障,确保修改立即刷回主内存;读操作前插入 LoadLoad + LoadStore 屏障,确保每次从主内存读取最新值。这保证了可见性(一个线程的修改对其他线程立即可见)和有序性(禁止指令重排序),但不保证原子性——count++ 包含读取、加1、写回三步操作,volatile 无法保证这三步作为原子执行。

// volatile:保证可见性和有序性
private volatile boolean running = true;

// 原子类:保证原子操作
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();  // 原子自增
count.compareAndSet(1, 2);  // CAS 操作
1
2
3
4
5
6
7
8
9

# 13.7.1 综合案例:并发计数器对比

本案例对比普通变量、volatile、synchronized 和原子类在并发场景下的表现。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 并发计数器对比 —— volatile和原子类综合案例
 * 知识点:volatile可见性、原子类CAS、synchronized对比
 */
public class CounterCompare {
    static int normalCount = 0;
    static volatile int volatileCount = 0;
    static int syncCount = 0;
    static AtomicInteger atomicCount = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        int threads = 10, perThread = 10000;
        System.out.println("===== 并发计数器对比 =====");
        System.out.println("线程数: " + threads + "  每线程累加: " + perThread + "\n");

        // 1. 普通变量(不安全)
        Thread[] t1 = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            t1[i] = new Thread(() -> {
                for (int j = 0; j < perThread; j++) normalCount++;
            });
            t1[i].start();
        }
        for (Thread t : t1) t.join();

        // 2. volatile(不安全:可见但非原子)
        Thread[] t2 = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            t2[i] = new Thread(() -> {
                for (int j = 0; j < perThread; j++) volatileCount++;
            });
            t2[i].start();
        }
        for (Thread t : t2) t.join();

        // 3. synchronized(安全)
        Thread[] t3 = new Thread[threads];
        Object lock = new Object();
        for (int i = 0; i < threads; i++) {
            t3[i] = new Thread(() -> {
                for (int j = 0; j < perThread; j++) {
                    synchronized (lock) { syncCount++; }
                }
            });
            t3[i].start();
        }
        for (Thread t : t3) t.join();

        // 4. AtomicInteger(安全且高效)
        Thread[] t4 = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            t4[i] = new Thread(() -> {
                for (int j = 0; j < perThread; j++) atomicCount.incrementAndGet();
            });
            t4[i].start();
        }
        for (Thread t : t4) t.join();

        int expected = threads * perThread;
        System.out.printf("  普通变量    : %d (期望%d) %s%n", normalCount, expected,
                normalCount == expected ? "✓" : "✗ 数据竞争");
        System.out.printf("  volatile   : %d (期望%d) %s%n", volatileCount, expected,
                volatileCount == expected ? "✓" : "✗ 非原子操作");
        System.out.printf("  synchronized: %d (期望%d) ✓%n", syncCount, expected);
        System.out.printf("  AtomicInteger: %d (期望%d) ✓%n", atomicCount.get(), expected);

        System.out.println("\n--- 总结 ---");
        System.out.println("  volatile : 保证可见性,不保证原子性(++不是原子操作)");
        System.out.println("  synchronized: 保证原子性+可见性,但有锁开销");
        System.out.println("  AtomicInteger: CAS无锁,高并发下性能更好");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 13.7.2 训练题

  1. 实践题:编写代码验证 volatile 不保证原子性——创建 10 个线程,每个线程对一个 volatile int count 做 1000 次 count++,观察最终结果是否为 10000。然后用 AtomicInteger 改写并对比。

  2. 代码题:以下代码中 volatile 起到了什么作用?如果去掉 volatile,可能出现什么问题?

    class StopTask implements Runnable {
        volatile boolean stopped = false;
        public void run() {
            while (!stopped) { /* 工作中 */ }
            System.out.println("线程已停止");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
  3. 思考题:CAS(Compare And Swap)是原子类的底层实现原理。请解释 CAS 的工作流程,以及它存在的 ABA 问题是什么?Java 如何通过 AtomicStampedReference 解决 ABA 问题?

上次更新: 2026/06/10, 11:13:41
IO流和File
泛型

← IO流和File 泛型→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式