线程和锁
# 13.线程和锁
# 目录介绍
# 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)
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();
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
2
3
4
5
6
7
8
9
10
11
# 13.1.5 线程生命周期
新建(New) → start() → 就绪(Runnable) → 获得CPU → 运行(Running)
↓
阻塞(Blocked/Waiting/Timed_Waiting)
↓
终止(Terminated)
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 : 有返回值,可抛异常");
}
}
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 训练题
实践题:分别使用继承
Thread、实现Runnable、实现Callable三种方式创建线程,每个线程打印 1~10。对比三种方式的代码量和灵活性。代码题:以下代码的输出是什么?
start()和run()有什么区别?Thread t = new Thread(() -> System.out.println("子线程:" + Thread.currentThread().getName())); t.run(); // 直接调用 run t.start(); // 启动线程1
2
3思考题:Java 线程有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),请画出它们之间的转换关系,并说明哪些方法/操作会触发状态转换。
# 13.2 线程控制
# 13.2.1 线程休眠sleep
Thread.sleep(1000); // 当前线程休眠1秒
# 13.2.2 线程等待join
Thread t = new Thread(() -> {
System.out.println("子线程执行");
});
t.start();
t.join(); // 主线程等待 t 执行完毕
System.out.println("子线程执行完了");
2
3
4
5
6
# 13.2.3 线程让步yield
Thread.yield(); // 让出CPU时间片,让其他线程执行
# 13.2.4 守护线程
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
}
});
daemon.setDaemon(true); // 设为守护线程
daemon.start();
// 主线程结束后,守护线程自动终止
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(" 主线程即将结束,守护线程自动终止");
}
}
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;
}
}
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;
}
}
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;
}
}
}
}
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对象(类级别)");
}
}
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;
}
}
2
3
4
5
6
7
8
9
10
11
疑惑:synchronized 经常被说"性能差",那到底差多少?
答疑:这是一个过时的认知。JDK 6 之前 synchronized 确实每次都走重量级锁(OS 互斥量),涉及用户态→内核态切换。但 JDK 6+ 引入了锁升级机制后,在低竞争场景下性能已经很好:
论证——锁升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
│ │ │ │
│ 零开销 CAS自旋 OS Mutex
│ 记录线程ID 短期竞争 线程阻塞
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 中释放锁
}
}
}
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();
}
}
}
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("读写锁优势: 多线程可同时读,只有写时才独占");
}
}
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(); // 唤醒其他等待的线程
}
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;
}
}
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完成");
}
}
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 训练题
实践题:使用
wait/notify实现两个线程交替打印 1~100(线程A打印奇数,线程B打印偶数)。代码题:在生产者消费者模型中,为什么
wait()要放在while循环中而不是if判断中?编写代码说明如果用if会出现什么问题(提示:虚假唤醒 spurious wakeup)。思考题:除了
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(); // 关闭线程池
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() // 拒绝策略
);
2
3
4
5
6
7
8
线程池任务处理流程(疑惑—答疑—论证—结果):
疑惑:任务提交后,线程池到底是先创建线程还是先放队列?
答疑:既不是"先创建"也不是"先放队列",而是分三个阶段:
论证:
提交任务 submit(task)
│
├─ 当前线程数 < corePoolSize?→ 是 → 创建核心线程执行
│
├─ 任务队列未满?→ 是 → 放入队列等待
│
├─ 当前线程数 < maximumPoolSize?→ 是 → 创建非核心线程执行
│
└─ 执行拒绝策略(AbortPolicy/CallerRunsPolicy/DiscardPolicy/DiscardOldestPolicy)
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(" 线程池已关闭");
}
}
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 操作
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无锁,高并发下性能更好");
}
}
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 训练题
实践题:编写代码验证
volatile不保证原子性——创建 10 个线程,每个线程对一个volatile int count做 1000 次count++,观察最终结果是否为 10000。然后用AtomicInteger改写并对比。代码题:以下代码中
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思考题:CAS(Compare And Swap)是原子类的底层实现原理。请解释 CAS 的工作流程,以及它存在的 ABA 问题是什么?Java 如何通过
AtomicStampedReference解决 ABA 问题?