1. 线程死锁
1.1 构成死锁的场景
a) 一个线程一把锁 - Java的可重入机制
java
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker) {
synchronized (locker) { // 同一个锁,可重入
System.out.println("成功进入嵌套同步块");
}
}
});
t1.start();
t1.join();
System.out.println("程序正常结束");
}
关键点:Java的synchronized锁是可重入的,同一个线程可以多次获取同一把锁
b) 两个线程两把锁 - 经典死锁场景
java
public class DeadlockDemo {
public static void main(String[] args) throws InterruptedException {
Object soySauce = new Object(); // 酱油锁
Object vinegar = new Object(); // 醋锁
Thread xiaoming = new Thread(() -> {
synchronized (soySauce) {
System.out.println("小明拿到了酱油");
try {
Thread.sleep(1000); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (vinegar) {
System.out.println("小明拿到了醋和酱油");
}
}
}, "小明");
Thread xiaoliang = new Thread(() -> {
synchronized (vinegar) {
System.out.println("小亮拿到了醋");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (soySauce) {
System.out.println("小亮拿到了酱油和醋");
}
}
}, "小亮");
xiaoming.start();
xiaoliang.start();
xiaoming.join();
xiaoliang.join();
System.out.println("程序结束"); // 这行可能永远不会执行
}
}
c) N个线程M把锁 - 哲学家就餐问题
java
public class PhilosopherDining {
private static final int PHILOSOPHER_COUNT = 5;
private static final Object[] chopsticks = new Object[PHILOSOPHER_COUNT];
static {
for (int i = 0; i < PHILOSOPHER_COUNT; i++) {
chopsticks[i] = new Object();
}
}
public static void main(String[] args) {
for (int i = 0; i < PHILOSOPHER_COUNT; i++) {
final int philosopherId = i;
new Thread(() -> {
Object leftChopstick = chopsticks[philosopherId];
Object rightChopstick = chopsticks[(philosopherId + 1) % PHILOSOPHER_COUNT];
while (true) {
synchronized (leftChopstick) {
synchronized (rightChopstick) {
System.out.println("哲学家 " + philosopherId + " 开始用餐");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 思考
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "哲学家-" + i).start();
}
}
}
1.2 死锁的四个必要条件
- 互斥 - 资源不能被共享,只能独占
- 不可剥夺 - 资源只能由持有者释放,不能被强制抢占
- 请求和保持 - 持有资源的同时请求其他资源
- 循环等待 - 等待关系形成环状
1.3 如何避免死锁
a) 打破请求和保持 - 使用并列锁
java
public class AvoidDeadlockByParallel {
public static void main(String[] args) {
Object soySauce = new Object();
Object vinegar = new Object();
Thread xiaoming = new Thread(() -> {
// 先释放酱油锁,再重新获取两个锁
synchronized (soySauce) {
System.out.println("小明拿到酱油");
}
try {
Thread.sleep(100); // 给其他线程机会
} catch (InterruptedException e) {
e.printStackTrace();
}
// 并列获取两个锁
synchronized (soySauce) {
synchronized (vinegar) {
System.out.println("小明同时拿到酱油和醋");
}
}
}, "小明");
Thread xiaoliang = new Thread(() -> {
synchronized (vinegar) {
System.out.println("小亮拿到醋");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (soySauce) {
synchronized (vinegar) {
System.out.println("小亮同时拿到酱油和醋");
}
}
}, "小亮");
xiaoming.start();
xiaoliang.start();
}
}
b) 打破循环等待 - 统一加锁顺序
java
public class AvoidDeadlockByOrder {
public static void main(String[] args) {
Object soySauce = new Object();
Object vinegar = new Object();
// 定义资源顺序:先酱油后醋
Thread xiaoming = new Thread(() -> {
acquireLocksInOrder(soySauce, vinegar, "小明");
}, "小明");
Thread xiaoliang = new Thread(() -> {
acquireLocksInOrder(soySauce, vinegar, "小亮");
}, "小亮");
xiaoming.start();
xiaoliang.start();
}
private static void acquireLocksInOrder(Object first, Object second, String name) {
synchronized (first) {
System.out.println(name + "拿到第一个资源");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (second) {
System.out.println(name + "同时拿到两个资源");
}
}
}
}
c) 使用超时机制
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutDeadlockAvoidance {
private static final Lock soySauce = new ReentrantLock();
private static final Lock vinegar = new ReentrantLock();
public static void main(String[] args) {
Thread xiaoming = new Thread(() -> {
try {
if (soySauce.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("小明拿到酱油");
Thread.sleep(1000);
if (vinegar.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("小明拿到醋和酱油");
} finally {
vinegar.unlock();
}
} else {
System.out.println("小明获取醋超时");
}
} finally {
soySauce.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小明");
Thread xiaoliang = new Thread(() -> {
// 类似的超时逻辑
}, "小亮");
xiaoming.start();
xiaoliang.start();
}
}
2. 内存可见性问题
可见性问题示例
java
public class VisibilityProblem {
static int counter = 0;
static boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) {
// 空循环 - 编译器可能优化为只读取一次running
}
System.out.println("Worker thread finished");
});
worker.start();
Thread.sleep(1000);
// 主线程修改标志位
running = false;
System.out.println("Main thread set running to false");
worker.join();
System.out.println("Main thread finished");
}
}
问题原因:
- 编译器优化:将循环条件缓存到寄存器
- CPU缓存:线程在自己的缓存中读取数据,不感知主内存变化
- 指令重排序:编译器/CPU可能重新安排指令执行顺序
3. volatile 关键字
3.1 volatile 保证内存可见性
java
public class VolatileVisibility {
static volatile boolean running = true; // 添加volatile
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) {
// 现在能正确感知running的变化
}
System.out.println("Worker thread finished");
});
worker.start();
Thread.sleep(1000);
running = false;
System.out.println("Main thread set running to false");
worker.join();
System.out.println("Main thread finished");
}
}
3.2 volatile 不保证原子性
java
public class VolatileNonAtomic {
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count++; // 这不是原子操作
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count); // 结果可能小于200000
}
}
volatile 的正确使用场景
java
public class VolatileCorrectUsage {
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true; // 写操作
}
public void doWork() {
while (!shutdownRequested) { // 读操作
// 执行工作
}
}
// 状态标志位
private volatile boolean initialized = false;
public void initialize() {
if (!initialized) {
synchronized (this) {
if (!initialized) {
// 初始化代码
initialized = true; // 发布初始化完成
}
}
}
}
}
4. wait 和 notify 机制
4.1 wait() 方法基础
java
public class WaitBasic {
public static void main(String[] args) {
Object locker = new Object();
Thread waiter = new Thread(() -> {
synchronized (locker) {
System.out.println("线程进入同步块,开始等待");
try {
locker.wait(); // 释放锁并等待
System.out.println("线程被唤醒,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waiter.start();
// 主线程3秒后唤醒等待线程
try {
Thread.sleep(3000);
synchronized (locker) {
locker.notify();
System.out.println("主线程发送通知");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.2 带超时的wait()
java
public class WaitWithTimeout {
public static void main(String[] args) {
Object locker = new Object();
Thread waiter = new Thread(() -> {
synchronized (locker) {
System.out.println("开始等待,超时时间2秒");
try {
long start = System.currentTimeMillis();
locker.wait(2000); // 最多等待2秒
long end = System.currentTimeMillis();
System.out.println("等待结束,耗时: " + (end - start) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waiter.start();
}
}
4.3 notify() 随机唤醒
java
public class NotifyRandom {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
for (int i = 1; i <= 3; i++) {
final int threadId = i;
new Thread(() -> {
synchronized (locker) {
try {
System.out.println("线程" + threadId + "开始等待");
locker.wait();
System.out.println("线程" + threadId + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(1000);
synchronized (locker) {
System.out.println("主线程发送单个notify");
locker.notify(); // 随机唤醒一个线程
}
Thread.sleep(1000);
synchronized (locker) {
System.out.println("主线程再发送一个notify");
locker.notify(); // 再唤醒一个
}
}
}
4.4 notifyAll() 唤醒所有
java
public class NotifyAllDemo {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
for (int i = 1; i <= 3; i++) {
final int threadId = i;
new Thread(() -> {
synchronized (locker) {
try {
System.out.println("线程" + threadId + "开始等待");
locker.wait();
System.out.println("线程" + threadId + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(1000);
synchronized (locker) {
System.out.println("主线程发送notifyAll");
locker.notifyAll(); // 唤醒所有等待线程
}
}
}
4.5 生产者和消费者模式
java
public class ProducerConsumer {
private static final int BUFFER_SIZE = 5;
private static final Queue<Integer> buffer = new LinkedList<>();
private static final Object lock = new Object();
static class Producer extends Thread {
public void run() {
int value = 0;
while (true) {
synchronized (lock) {
// 缓冲区满时等待
while (buffer.size() == BUFFER_SIZE) {
try {
System.out.println("缓冲区满,生产者等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产数据
buffer.offer(value);
System.out.println("生产: " + value);
value++;
// 通知消费者
lock.notifyAll();
}
try {
Thread.sleep(500); // 模拟生产时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer extends Thread {
public void run() {
while (true) {
synchronized (lock) {
// 缓冲区空时等待
while (buffer.isEmpty()) {
try {
System.out.println("缓冲区空,消费者等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费数据
int value = buffer.poll();
System.out.println("消费: " + value);
// 通知生产者
lock.notifyAll();
}
try {
Thread.sleep(1000); // 模拟消费时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new Producer().start();
new Consumer().start();
}
}
4.6 wait 和 sleep 对比总结
| 特性 | wait() | sleep() |
|---|---|---|
| 所属类 | Object | Thread |
| 锁释放 | 释放锁 | 不释放锁 |
| 使用条件 | 必须在同步块内 | 任何地方 |
| 唤醒方式 | notify()/notifyAll()/超时 | 超时/interrupt() |
| 异常处理 | 需要处理InterruptedException | 需要处理InterruptedException |
| 用途 | 线程间协调 | 单纯暂停执行 |
java
public class WaitVsSleep {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// wait示例 - 会释放锁
Thread waitThread = new Thread(() -> {
synchronized (lock) {
System.out.println("waitThread获取锁,开始wait");
try {
lock.wait(2000); // 释放锁,等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waitThread被唤醒");
}
});
// sleep示例 - 不会释放锁
Thread sleepThread = new Thread(() -> {
synchronized (lock) {
System.out.println("sleepThread获取锁,开始sleep");
try {
Thread.sleep(2000); // 不释放锁,休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleepThread醒来");
}
});
waitThread.start();
Thread.sleep(100); // 确保waitThread先启动
sleepThread.start();
}
}
总结
关键要点:
-
死锁预防:
- 统一加锁顺序
- 使用超时机制
- 避免嵌套锁
-
内存可见性:
- volatile保证可见性,不保证原子性
- 适合状态标志位场景
-
线程协调:
- wait/notify用于线程间通信
- 总是要在循环中检查条件
- 理解wait会释放锁,sleep不会
-
最佳实践:
- 尽量减少锁的持有时间
- 使用更高级的并发工具(如Lock、Condition)
- 合理设计程序结构,避免复杂的锁依赖
掌握这些多线程核心概念,能够帮助你编写出更安全、高效的多线程程序!