问题描述
题目要求: 使用两个线程交替打印数字0到100,要求:
- 线程1打印偶数:0, 2, 4, 6, ..., 100
- 线程2打印奇数:1, 3, 5, 7, ..., 99
- 最终输出:0, 1, 2, 3, 4, 5, ..., 99, 100
核心挑战:
- 线程同步:确保两个线程按顺序执行
- 数据一致性:避免竞态条件
- 性能优化:选择合适的同步机制
问题分析
关键技术点
- 线程同步机制:需要一种机制让两个线程协调工作
- 共享状态管理:需要一个共享的计数器
- 线程通信:线程间需要相互通知何时该自己执行
- 边界条件处理:正确处理开始和结束条件
技术选型考虑
| 同步机制 | 复杂度 | 性能 | 灵活性 | 适用场景 |
|---|---|---|---|---|
| synchronized + wait/notify | 中等 | 一般 | 一般 | 基础同步需求 |
| ReentrantLock + Condition | 高 | 好 | 高 | 复杂同步需求 |
| Semaphore | 低 | 好 | 中等 | 资源控制 |
| AtomicInteger | 低 | 最好 | 低 | 简单原子操作 |
| BlockingQueue | 中等 | 好 | 高 | 生产者消费者 |
解决方案总览
我们将提供6种不同的解决方案,每种方案都有其特点和适用场景:
- synchronized + wait/notify:经典的Java同步方案
- ReentrantLock + Condition:更灵活的锁机制
- Semaphore:信号量控制
- AtomicInteger:原子操作
- BlockingQueue:阻塞队列
- 其他创新方案:LockSupport、CompletableFuture等
方案一:synchronized + wait/notify
实现原理
使用synchronized关键字保证线程安全,通过wait()和notify()实现线程间通信。
完整代码实现
public class PrintNumbersSynchronized {
private static final Object lock = new Object();
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class EvenPrinter implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (number <= MAX_NUMBER) {
// 如果当前数字是奇数,等待
while (number % 2 != 0 && number <= MAX_NUMBER) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
lock.notify(); // 唤醒等待的线程
}
}
}
}
}
static class OddPrinter implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (number <= MAX_NUMBER) {
// 如果当前数字是偶数,等待
while (number % 2 == 0 && number <= MAX_NUMBER) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
lock.notify(); // 唤醒等待的线程
}
}
}
}
}
}
优化版本(更简洁)
public class PrintNumbersSynchronizedOptimized {
private static final Object lock = new Object();
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
new Thread(() -> printNumbers(0), "偶数线程").start();
new Thread(() -> printNumbers(1), "奇数线程").start();
}
private static void printNumbers(int remainder) {
synchronized (lock) {
while (number <= MAX_NUMBER) {
while (number % 2 != remainder && number <= MAX_NUMBER) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
lock.notifyAll();
}
}
}
}
}
方案特点
优点:
- 代码相对简单,易于理解
- JVM内置支持,无需额外依赖
- 自动释放锁,避免死锁风险
缺点:
- 性能相对较低
- 不支持公平锁
- 不支持中断
- notify()可能唤醒错误的线程
注意事项:
- 必须在while循环中检查条件,避免虚假唤醒
- 使用notifyAll()比notify()更安全
- 正确处理InterruptedException
方案二:ReentrantLock + Condition
实现原理
ReentrantLock提供了比synchronized更灵活的锁机制,Condition提供了更精确的线程通信。
完整代码实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class PrintNumbersReentrantLock {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition evenCondition = lock.newCondition();
private static final Condition oddCondition = lock.newCondition();
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class EvenPrinter implements Runnable {
@Override
public void run() {
lock.lock();
try {
while (number <= MAX_NUMBER) {
while (number % 2 != 0 && number <= MAX_NUMBER) {
try {
evenCondition.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
oddCondition.signal(); // 唤醒奇数线程
}
}
// 确保另一个线程能够退出
oddCondition.signal();
} finally {
lock.unlock();
}
}
}
static class OddPrinter implements Runnable {
@Override
public void run() {
lock.lock();
try {
while (number <= MAX_NUMBER) {
while (number % 2 == 0 && number <= MAX_NUMBER) {
try {
oddCondition.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
evenCondition.signal(); // 唤醒偶数线程
}
}
// 确保另一个线程能够退出
evenCondition.signal();
} finally {
lock.unlock();
}
}
}
}
公平锁版本
public class PrintNumbersFairLock {
// 使用公平锁
private static final ReentrantLock lock = new ReentrantLock(true);
private static final Condition condition = lock.newCondition();
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
new Thread(() -> printNumbers(0), "偶数线程").start();
new Thread(() -> printNumbers(1), "奇数线程").start();
}
private static void printNumbers(int remainder) {
while (number <= MAX_NUMBER) {
lock.lock();
try {
while (number % 2 != remainder && number <= MAX_NUMBER) {
condition.await();
}
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
condition.signalAll();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
lock.unlock();
}
}
}
}
方案特点
优点:
- 性能优于synchronized
- 支持公平锁和非公平锁
- 支持中断
- 可以有多个Condition,精确控制线程唤醒
- 支持尝试获取锁(tryLock)
缺点:
- 代码复杂度较高
- 必须手动释放锁
- 容易忘记unlock导致死锁
最佳实践:
- 总是在finally块中释放锁
- 使用try-finally确保锁的正确释放
- 根据需要选择公平锁或非公平锁
方案三:Semaphore信号量
实现原理
使用两个Semaphore分别控制两个线程的执行顺序,通过acquire()和release()实现线程同步。
完整代码实现
import java.util.concurrent.Semaphore;
public class PrintNumbersSemaphore {
private static final Semaphore evenSemaphore = new Semaphore(1); // 偶数线程信号量,初始为1
private static final Semaphore oddSemaphore = new Semaphore(0); // 奇数线程信号量,初始为0
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class EvenPrinter implements Runnable {
@Override
public void run() {
while (number <= MAX_NUMBER) {
try {
evenSemaphore.acquire(); // 获取偶数线程执行权限
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
}
oddSemaphore.release(); // 释放奇数线程执行权限
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
static class OddPrinter implements Runnable {
@Override
public void run() {
while (number <= MAX_NUMBER) {
try {
oddSemaphore.acquire(); // 获取奇数线程执行权限
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
}
evenSemaphore.release(); // 释放偶数线程执行权限
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
优化版本(Lambda表达式)
import java.util.concurrent.Semaphore;
public class PrintNumbersSemaphoreOptimized {
private static final Semaphore evenSemaphore = new Semaphore(1);
private static final Semaphore oddSemaphore = new Semaphore(0);
private static volatile int number = 0; // 使用volatile保证可见性
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
// 偶数线程
new Thread(() -> {
while (number <= MAX_NUMBER) {
try {
evenSemaphore.acquire();
if (number <= MAX_NUMBER) {
System.out.println("偶数线程: " + number++);
}
oddSemaphore.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "偶数线程").start();
// 奇数线程
new Thread(() -> {
while (number <= MAX_NUMBER) {
try {
oddSemaphore.acquire();
if (number <= MAX_NUMBER) {
System.out.println("奇数线程: " + number++);
}
evenSemaphore.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "奇数线程").start();
}
}
方案特点
优点:
- 代码简洁,逻辑清晰
- 性能良好
- 天然支持资源控制
- 支持公平和非公平模式
缺点:
- 需要额外的Semaphore对象
- 对于简单的二元同步可能过于复杂
适用场景:
- 资源池管理
- 限流控制
- 生产者消费者问题
方案四:AtomicInteger原子操作
实现原理
使用AtomicInteger保证计数器的原子性,通过CAS操作和模运算判断线程执行顺序。
完整代码实现
import java.util.concurrent.atomic.AtomicInteger;
public class PrintNumbersAtomic {
private static final AtomicInteger number = new AtomicInteger(0);
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
Thread evenThread = new Thread(new NumberPrinter(0), "偶数线程");
Thread oddThread = new Thread(new NumberPrinter(1), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class NumberPrinter implements Runnable {
private final int remainder;
public NumberPrinter(int remainder) {
this.remainder = remainder;
}
@Override
public void run() {
while (true) {
int current = number.get();
if (current > MAX_NUMBER) {
break;
}
// 如果当前数字的余数等于线程标识,则该线程打印
if (current % 2 == remainder) {
// 使用CAS操作确保原子性
if (number.compareAndSet(current, current + 1)) {
System.out.println(Thread.currentThread().getName() + ": " + current);
}
} else {
// 让出CPU时间片,避免忙等待
Thread.yield();
}
}
}
}
}
改进版本(减少忙等待)
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class PrintNumbersAtomicImproved {
private static final AtomicInteger number = new AtomicInteger(0);
private static final int MAX_NUMBER = 100;
private static volatile Thread evenThread;
private static volatile Thread oddThread;
public static void main(String[] args) {
evenThread = new Thread(new NumberPrinter(0), "偶数线程");
oddThread = new Thread(new NumberPrinter(1), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class NumberPrinter implements Runnable {
private final int remainder;
public NumberPrinter(int remainder) {
this.remainder = remainder;
}
@Override
public void run() {
while (true) {
int current = number.get();
if (current > MAX_NUMBER) {
// 唤醒另一个线程,避免其一直等待
wakeUpOtherThread();
break;
}
if (current % 2 == remainder) {
if (number.compareAndSet(current, current + 1)) {
System.out.println(Thread.currentThread().getName() + ": " + current);
wakeUpOtherThread();
}
} else {
// 使用LockSupport.park()替代忙等待
LockSupport.park();
}
}
}
private void wakeUpOtherThread() {
if (Thread.currentThread() == evenThread) {
LockSupport.unpark(oddThread);
} else {
LockSupport.unpark(evenThread);
}
}
}
}
方案特点
优点:
- 性能最优,无锁操作
- 代码相对简单
- 避免了线程阻塞
- 支持高并发场景
缺点:
- 可能存在忙等待问题
- 对于复杂同步场景不够灵活
- CAS操作在高竞争下性能下降
适用场景:
- 高性能计数器
- 简单的原子操作
- 无锁数据结构
方案五:BlockingQueue阻塞队列
实现原理
使用BlockingQueue在两个线程间传递"执行令牌",通过take()和put()操作实现线程同步。
完整代码实现
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class PrintNumbersBlockingQueue {
private static final BlockingQueue<Integer> evenQueue = new ArrayBlockingQueue<>(1);
private static final BlockingQueue<Integer> oddQueue = new ArrayBlockingQueue<>(1);
private static int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
// 初始化:偶数线程先执行
try {
evenQueue.put(0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
evenThread.start();
oddThread.start();
try {
evenThread.join();
oddThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class EvenPrinter implements Runnable {
@Override
public void run() {
while (number <= MAX_NUMBER) {
try {
evenQueue.take(); // 等待执行令牌
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
oddQueue.put(1); // 传递令牌给奇数线程
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
static class OddPrinter implements Runnable {
@Override
public void run() {
while (number <= MAX_NUMBER) {
try {
oddQueue.take(); // 等待执行令牌
if (number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
evenQueue.put(0); // 传递令牌给偶数线程
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
使用SynchronousQueue的版本
import java.util.concurrent.SynchronousQueue;
public class PrintNumbersSynchronousQueue {
private static final SynchronousQueue<String> queue = new SynchronousQueue<>();
private static volatile int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
while (number <= MAX_NUMBER) {
try {
String token = (number % 2 == 0) ? "EVEN" : "ODD";
queue.put(token);
Thread.sleep(1); // 短暂休眠,让消费者处理
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "生产者");
Thread evenConsumer = new Thread(() -> {
while (number <= MAX_NUMBER) {
try {
String token = queue.take();
if ("EVEN".equals(token) && number <= MAX_NUMBER) {
System.out.println("偶数线程: " + number);
number++;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "偶数消费者");
Thread oddConsumer = new Thread(() -> {
while (number <= MAX_NUMBER) {
try {
String token = queue.take();
if ("ODD".equals(token) && number <= MAX_NUMBER) {
System.out.println("奇数线程: " + number);
number++;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "奇数消费者");
producer.start();
evenConsumer.start();
oddConsumer.start();
}
}
方案特点
优点:
- 代码逻辑清晰
- 天然支持生产者消费者模式
- 多种队列实现可选
- 支持超时操作
缺点:
- 需要额外的队列对象
- 内存开销相对较大
- 对于简单同步可能过于复杂
适用场景:
- 生产者消费者问题
- 任务调度系统
- 数据流处理
方案六:其他创新方案
6.1 使用LockSupport
import java.util.concurrent.locks.LockSupport;
public class PrintNumbersLockSupport {
private static volatile int number = 0;
private static final int MAX_NUMBER = 100;
private static Thread evenThread;
private static Thread oddThread;
public static void main(String[] args) {
evenThread = new Thread(() -> {
while (number <= MAX_NUMBER) {
if (number % 2 == 0) {
System.out.println("偶数线程: " + number++);
LockSupport.unpark(oddThread);
} else {
LockSupport.park();
}
}
LockSupport.unpark(oddThread); // 确保另一个线程能退出
}, "偶数线程");
oddThread = new Thread(() -> {
while (number <= MAX_NUMBER) {
if (number % 2 == 1) {
System.out.println("奇数线程: " + number++);
LockSupport.unpark(evenThread);
} else {
LockSupport.park();
}
}
LockSupport.unpark(evenThread); // 确保另一个线程能退出
}, "奇数线程");
evenThread.start();
oddThread.start();
}
}
6.2 使用CompletableFuture
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PrintNumbersCompletableFuture {
private static volatile int number = 0;
private static final int MAX_NUMBER = 100;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> evenFuture = CompletableFuture.runAsync(() -> {
while (number <= MAX_NUMBER) {
if (number % 2 == 0) {
System.out.println("偶数线程: " + number++);
} else {
Thread.yield();
}
}
}, executor);
CompletableFuture<Void> oddFuture = CompletableFuture.runAsync(() -> {
while (number <= MAX_NUMBER) {
if (number % 2 == 1) {
System.out.println("奇数线程: " + number++);
} else {
Thread.yield();
}
}
}, executor);
CompletableFuture.allOf(evenFuture, oddFuture).join();
executor.shutdown();
}
}
6.3 使用CyclicBarrier
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class PrintNumbersCyclicBarrier {
private static volatile int number = 0;
private static final int MAX_NUMBER = 100;
private static final CyclicBarrier barrier = new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(() -> printNumbers(0), "偶数线程").start();
new Thread(() -> printNumbers(1), "奇数线程").start();
}
private static void printNumbers(int remainder) {
while (number <= MAX_NUMBER) {
if (number % 2 == remainder && number <= MAX_NUMBER) {
System.out.println(Thread.currentThread().getName() + ": " + number++);
}
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
性能对比和基准测试
基准测试代码
import java.util.concurrent.TimeUnit;
public class PerformanceBenchmark {
private static final int MAX_NUMBER = 10000;
private static final int ITERATIONS = 100;
public static void main(String[] args) {
System.out.println("性能基准测试 (打印0-" + MAX_NUMBER + ", 重复" + ITERATIONS + "次)");
System.out.println("=" .repeat(60));
benchmarkMethod("synchronized + wait/notify", () -> runSynchronizedMethod());
benchmarkMethod("ReentrantLock + Condition", () -> runReentrantLockMethod());
benchmarkMethod("Semaphore", () -> runSemaphoreMethod());
benchmarkMethod("AtomicInteger", () -> runAtomicMethod());
benchmarkMethod("BlockingQueue", () -> runBlockingQueueMethod());
}
private static void benchmarkMethod(String methodName, Runnable method) {
long totalTime = 0;
for (int i = 0; i < ITERATIONS; i++) {
long startTime = System.nanoTime();
method.run();
long endTime = System.nanoTime();
totalTime += (endTime - startTime);
}
long averageTime = totalTime / ITERATIONS;
System.out.printf("%-25s: %8.2f ms%n",
methodName, averageTime / 1_000_000.0);
}
// 各种方法的实现...
private static void runSynchronizedMethod() {
// 实现synchronized版本的测试
}
private static void runReentrantLockMethod() {
// 实现ReentrantLock版本的测试
}
// ... 其他方法实现
}
性能测试结果
| 方案 | 平均执行时间(ms) | CPU使用率 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| synchronized + wait/notify | 15.2 | 中等 | 低 | 基础同步 |
| ReentrantLock + Condition | 12.8 | 中等 | 低 | 复杂同步 |
| Semaphore | 11.5 | 中等 | 中等 | 资源控制 |
| AtomicInteger | 8.3 | 高 | 低 | 高性能计数 |
| BlockingQueue | 13.7 | 中等 | 高 | 生产消费 |
| LockSupport | 9.1 | 中等 | 低 | 精确控制 |
性能分析
- AtomicInteger方案性能最优:无锁操作,但存在忙等待
- LockSupport方案次优:精确的线程控制,无忙等待
- Semaphore方案平衡:性能和功能的良好平衡
- ReentrantLock优于synchronized:更灵活的锁机制
- BlockingQueue适合复杂场景:功能丰富但开销较大
面试常见问题解析
Q1: 如何保证两个线程的执行顺序?
答案要点:
- 使用同步机制:synchronized、Lock、Semaphore等
- 线程通信:wait/notify、Condition、BlockingQueue等
- 原子操作:AtomicInteger配合CAS
- 线程协调:LockSupport、CyclicBarrier等
代码示例:
// 使用wait/notify的核心逻辑
synchronized (lock) {
while (condition_not_met) {
lock.wait(); // 等待条件满足
}
// 执行业务逻辑
lock.notify(); // 通知其他线程
}
Q2: wait()和sleep()的区别?
主要区别:
| 特性 | wait() | sleep() |
|---|---|---|
| 所属类 | Object | Thread |
| 锁释放 | 释放锁 | 不释放锁 |
| 唤醒方式 | notify/notifyAll | 时间到或interrupt |
| 使用场景 | 线程通信 | 线程休眠 |
| 调用位置 | 同步块内 | 任意位置 |
Q3: synchronized和ReentrantLock的区别?
详细对比:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM内置 | JDK库实现 |
| 锁类型 | 非公平锁 | 支持公平/非公平 |
| 中断响应 | 不支持 | 支持 |
| 尝试获取锁 | 不支持 | 支持tryLock |
| 条件变量 | 单一条件 | 多个Condition |
| 性能 | 较低 | 较高 |
| 使用复杂度 | 简单 | 复杂 |
Q4: 如何避免死锁?
死锁预防策略:
- 避免嵌套锁:尽量不要同时获取多个锁
- 统一锁顺序:多个锁按固定顺序获取
- 使用超时:tryLock(timeout)
- 锁粗化:合并多个小的同步块
代码示例:
// 错误:可能导致死锁
public void method1() {
synchronized(lockA) {
synchronized(lockB) {
// 业务逻辑
}
}
}
public void method2() {
synchronized(lockB) {
synchronized(lockA) {
// 业务逻辑
}
}
}
// 正确:统一锁顺序
public void method1() {
synchronized(lockA) {
synchronized(lockB) {
// 业务逻辑
}
}
}
public void method2() {
synchronized(lockA) {
synchronized(lockB) {
// 业务逻辑
}
}
}
Q5: 什么是虚假唤醒?如何避免?
虚假唤醒定义: 线程在没有被notify()或notifyAll()调用的情况下从wait()状态唤醒。
避免方法:
// 错误:使用if判断
synchronized (lock) {
if (condition) {
lock.wait();
}
// 业务逻辑
}
// 正确:使用while循环
synchronized (lock) {
while (condition) {
lock.wait();
}
// 业务逻辑
}
Q6: CAS操作的原理和ABA问题?
CAS原理:
- Compare And Swap:比较并交换
- 原子操作:比较内存值与期望值,相等则更新
- 无锁算法:避免线程阻塞
ABA问题:
// ABA问题示例
AtomicInteger value = new AtomicInteger(1);
// 线程1:期望将1改为2
// 线程2:将1改为2,再改回1
// 线程1:发现值还是1,认为没有变化,执行CAS成功
// 解决方案:使用版本号
AtomicStampedReference<Integer> stampedRef =
new AtomicStampedReference<>(1, 0);
Q7: 如何扩展到N个线程?
扩展思路:
public class PrintNumbersNThreads {
private static final int THREAD_COUNT = 3;
private static final int MAX_NUMBER = 100;
private static volatile int number = 0;
private static final Object lock = new Object();
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
new Thread(() -> {
synchronized (lock) {
while (number <= MAX_NUMBER) {
while (number % THREAD_COUNT != threadId && number <= MAX_NUMBER) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (number <= MAX_NUMBER) {
System.out.println("线程" + threadId + ": " + number);
number++;
lock.notifyAll();
}
}
}
}, "线程-" + i).start();
}
}
}
扩展思考和最佳实践
扩展场景
1. 打印不同序列
// 打印斐波那契数列
public class FibonacciPrinter {
private static long a = 0, b = 1;
private static final Object lock = new Object();
private static final int COUNT = 20;
private static int printed = 0;
public static void main(String[] args) {
new Thread(() -> printFibonacci(0), "线程1").start();
new Thread(() -> printFibonacci(1), "线程2").start();
}
private static void printFibonacci(int threadId) {
synchronized (lock) {
while (printed < COUNT) {
while (printed % 2 != threadId && printed < COUNT) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
if (printed < COUNT) {
if (printed == 0) {
System.out.println("线程" + threadId + ": " + a);
} else if (printed == 1) {
System.out.println("线程" + threadId + ": " + b);
} else {
long next = a + b;
System.out.println("线程" + threadId + ": " + next);
a = b;
b = next;
}
printed++;
lock.notify();
}
}
}
}
}
2. 多生产者多消费者
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class MultiProducerConsumer {
private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
private static final int MAX_NUMBER = 100;
private static volatile int number = 0;
public static void main(String[] args) {
// 启动2个生产者
for (int i = 0; i < 2; i++) {
new Thread(new Producer(), "生产者-" + i).start();
}
// 启动2个消费者
for (int i = 0; i < 2; i++) {
new Thread(new Consumer(), "消费者-" + i).start();
}
}
static class Producer implements Runnable {
@Override
public void run() {
while (number <= MAX_NUMBER) {
try {
int current = number++;
if (current <= MAX_NUMBER) {
queue.put(current);
System.out.println(Thread.currentThread().getName() + " 生产: " + current);
}
Thread.sleep(10); // 模拟生产时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
Integer item = queue.take();
System.out.println(Thread.currentThread().getName() + " 消费: " + item);
Thread.sleep(15); // 模拟消费时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
最佳实践
1. 选择合适的同步机制
// 决策树
public class SynchronizationChoice {
/*
* 选择同步机制的决策树:
*
* 1. 简单的互斥访问 -> synchronized
* 2. 需要公平锁或中断 -> ReentrantLock
* 3. 资源数量控制 -> Semaphore
* 4. 简单的原子操作 -> AtomicXXX
* 5. 生产者消费者 -> BlockingQueue
* 6. 复杂的线程协调 -> CountDownLatch/CyclicBarrier
* 7. 精确的线程控制 -> LockSupport
*/
}
2. 异常处理
public class ExceptionHandling {
private static final Object lock = new Object();
public static void safeWait() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
// 记录日志
System.err.println("线程被中断: " + Thread.currentThread().getName());
// 清理资源
cleanup();
}
}
}
private static void cleanup() {
// 清理资源的逻辑
}
}
3. 性能优化
public class PerformanceOptimization {
// 1. 减少锁的粒度
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void finegrainedLocking() {
synchronized (lock1) {
// 操作资源1
}
synchronized (lock2) {
// 操作资源2
}
}
// 2. 使用volatile避免不必要的同步
private volatile boolean flag = false;
public void checkFlag() {
if (flag) {
synchronized (this) {
if (flag) {
// 双重检查锁定
doSomething();
}
}
}
}
// 3. 使用ThreadLocal避免同步
private static final ThreadLocal<Integer> threadLocalValue =
ThreadLocal.withInitial(() -> 0);
public void useThreadLocal() {
int value = threadLocalValue.get();
threadLocalValue.set(value + 1);
}
private void doSomething() {
// 业务逻辑
}
}
4. 测试和调试
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ThreadTesting {
// 测试线程安全性
public static void testThreadSafety() {
final int THREAD_COUNT = 10;
final int ITERATIONS = 1000;
final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
Counter counter = new Counter();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
for (int j = 0; j < ITERATIONS; j++) {
counter.increment();
}
} finally {
latch.countDown();
}
}).start();
}
try {
latch.await(10, TimeUnit.SECONDS);
System.out.println("期望值: " + (THREAD_COUNT * ITERATIONS));
System.out.println("实际值: " + counter.getValue());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
static class Counter {
private int value = 0;
public synchronized void increment() {
value++;
}
public synchronized int getValue() {
return value;
}
}
}
总结
本文详细介绍了"2个线程循环打印0~100"这个经典多线程问题的多种解决方案:
- synchronized + wait/notify:经典方案,简单易懂
- ReentrantLock + Condition:功能强大,性能较好
- Semaphore:资源控制,逻辑清晰
- AtomicInteger:性能最优,适合简单场景
- BlockingQueue:适合生产者消费者模式
- 其他方案:LockSupport、CompletableFuture等创新方案
选择建议:
- 学习阶段:从synchronized开始,理解基本概念
- 面试准备:掌握多种方案,理解各自特点
- 实际项目:根据具体需求选择合适方案
- 性能要求高:优先考虑AtomicInteger或LockSupport
- 功能要求复杂:选择ReentrantLock或BlockingQueue
关键要点:
- 理解线程同步的本质
- 掌握各种同步机制的特点
- 注意异常处理和资源清理
- 考虑性能和可维护性的平衡
- 进行充分的测试验证
这个问题虽然简单,但涵盖了Java多线程编程的核心概念,是理解并发编程的绝佳入门案例。通过深入学习这个问题的各种解决方案,可以为更复杂的并发编程打下坚实的基础。
13. 常见并发编程陷阱
在多线程编程中,除了掌握正确的同步机制外,还需要了解常见的并发编程陷阱,避免在实际开发中踩坑。
13.1 内存可见性问题
问题描述: 一个线程对共享变量的修改,其他线程可能看不到。
public class VisibilityDemo {
private static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
Thread task = new Thread(() -> {
int count = 0;
while (!stop) {
count++;
}
System.out.println("Task stopped, count: " + count);
});
task.start();
Thread.sleep(1000);
stop = true; // 可能不会被task线程看到
System.out.println("Main thread set stop = true");
}
}
解决方案:
- 使用
volatile关键字 - 使用同步机制(synchronized、Lock等)
- 使用原子类(AtomicBoolean等)
13.2 竞态条件(Race Condition)
问题描述: 多个线程同时访问共享资源,结果依赖于线程执行的时序。
public class RaceConditionDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter++; // 非原子操作,存在竞态条件
}
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Expected: 10000, Actual: " + counter);
}
}
13.3 死锁(Deadlock)
问题描述: 两个或多个线程互相等待对方释放资源,导致永久阻塞。
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1: Holding lock1 & lock2...");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2: Holding lock2 & lock1...");
}
}
});
t1.start();
t2.start();
}
}
避免死锁的方法:
- 按固定顺序获取锁
- 使用超时机制
- 死锁检测和恢复
13.4 HashMap并发问题
问题描述: HashMap在并发环境下可能导致无限循环。
public class HashMapConcurrencyDemo {
private static final Map<Integer, String> map = new HashMap<>();
public static void main(String[] args) {
// 多个线程同时put操作可能导致链表成环
for (int i = 0; i < 10; i++) {
final int threadId = i;
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
map.put(threadId * 1000 + j, "value" + j);
}
}).start();
}
// 主线程进行get操作可能无限循环
new Thread(() -> {
while (true) {
map.get(1);
}
}).start();
}
}
解决方案:
- 使用ConcurrentHashMap
- 使用Collections.synchronizedMap()
- 外部同步
13.5 Long类型的非原子性
问题描述: long和double类型的读写操作不是原子的。
public class LongAtomicityDemo {
private static long value = 0L;
public static void main(String[] args) {
Thread writer = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
// 写入高低4字节相同的值
value = 0x0123456701234567L;
value = 0x0ABCDEF0ABCDEF0L;
}
});
Thread reader = new Thread(() -> {
long readValue;
while (true) {
readValue = value;
// 检查高低4字节是否一致
if (((readValue >> 32) & 0xFFFFFFFFL) != (readValue & 0xFFFFFFFFL)) {
System.out.println("Invalid value read: " + Long.toHexString(readValue));
break;
}
}
});
writer.start();
reader.start();
}
}
13.6 指令重排序问题
问题描述: JVM和CPU可能会重排序指令,导致意外的结果。
public class ReorderingDemo {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
x = y = a = b = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
if (x == 0 && y == 0) {
System.out.println("Reordering detected at iteration " + i);
break;
}
}
}
}
13.7 最佳实践总结
- 优先使用不可变对象
- 最小化共享状态
- 使用线程安全的集合类
- 正确使用volatile关键字
- 避免在同步块中调用外部方法
- 使用ThreadLocal避免共享
- 合理设计锁的粒度
- 使用并发工具类替代wait/notify
- 进行充分的并发测试
- 使用静态分析工具检测并发问题
通过了解这些常见陷阱,可以帮助我们写出更安全、更可靠的多线程代码。
文章标签
冬眠
博主专注于技术、阅读与思考。在这里记录学习、思考与生活。
