并发编程:像管理团队一样管理线程
一、并发 vs 并行 vs 串行:厨房工作模式
1.1 串行:一个人做完所有事
生活比喻:家里只有你一个人做饭,必须按顺序:洗菜 → 切菜 → 炒菜 → 装盘
java
public class SerialCooking {
public static void main(String[] args) {
System.out.println("👨🍳 开始做饭...");
washVegetables(); // 洗菜 - 3秒
cutVegetables(); // 切菜 - 2秒
cook(); // 炒菜 - 4秒
serve(); // 装盘 - 1秒
System.out.println("✅ 所有任务完成!");
}
static void washVegetables() {
try {
Thread.sleep(3000);
System.out.println("🧼 菜洗好了");
} catch (InterruptedException e) {}
}
static void cutVegetables() {
try {
Thread.sleep(2000);
System.out.println("🔪 菜切好了");
} catch (InterruptedException e) {}
}
static void cook() {
try {
Thread.sleep(4000);
System.out.println("🍳 菜炒好了");
} catch (InterruptedException e) {}
}
static void serve() {
try {
Thread.sleep(1000);
System.out.println("🍽️ 菜装盘了");
} catch (InterruptedException e) {}
}
}
结果:总共10秒,一个人干完所有活
1.2 并发:一个人轮流做多件事
生活比喻:你还是一个人,但边煮饭边准备其他菜,快速切换
java
public class ConcurrentCooking {
public static void main(String[] args) throws InterruptedException {
System.out.println("👨🍳 开始并发做饭...");
long startTime = System.currentTimeMillis();
Thread riceThread = new Thread(() -> {
try {
System.out.println("🍚 开始煮饭");
Thread.sleep(5000);
System.out.println("🍚 饭煮好了");
} catch (InterruptedException e) {}
});
Thread soupThread = new Thread(() -> {
try {
System.out.println("🍲 开始做汤");
Thread.sleep(3000);
System.out.println("🍲 汤做好了");
} catch (InterruptedException e) {}
});
Thread dishThread = new Thread(() -> {
try {
System.out.println("🥬 开始炒菜");
Thread.sleep(4000);
System.out.println("🥬 菜炒好了");
} catch (InterruptedException e) {}
});
riceThread.start();
soupThread.start();
dishThread.start();
// 等待所有线程完成
riceThread.join();
soupThread.join();
dishThread.join();
long endTime = System.currentTimeMillis();
System.out.println("✅ 并发做饭完成!总时间: " + (endTime - startTime)/1000 + "秒");
}
}
结果:大约5秒(最慢的任务时间),一个人快速切换任务
1.3 并行:团队协作
生活比喻:专业厨房,厨师、助手、装盘师同时工作
java
public class ParallelCooking {
public static void main(String[] args) throws InterruptedException {
System.out.println("👨🍳👩🍳 开始团队协作做饭...");
long startTime = System.currentTimeMillis();
// 创建线程池 - 像组建厨房团队
ExecutorService kitchenTeam = Executors.newFixedThreadPool(3);
// 分配任务 - 不同的人同时做不同的事
Future<?> chef = kitchenTeam.submit(() -> {
System.out.println("🧑🍳 大厨开始炒菜");
try { Thread.sleep(4000); } catch (InterruptedException e) {}
System.out.println("🧑🍳 大厨:菜炒好了");
});
Future<?> assistant = kitchenTeam.submit(() -> {
System.out.println("👨🍳 助手开始准备配菜");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("👨🍳 助手:配菜准备好了");
});
Future<?> server = kitchenTeam.submit(() -> {
System.out.println("👩🍳 装盘师准备餐具");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("👩🍳 装盘师:餐具准备好了");
});
// 等待所有任务完成
chef.get();
assistant.get();
server.get();
kitchenTeam.shutdown();
long endTime = System.currentTimeMillis();
System.out.println("🎉 团队协作完成!总时间: " + (endTime - startTime)/1000 + "秒");
}
}
结果:大约4秒(最慢的任务时间),多人真正同时工作
二、并发问题:当多个人操作同一个东西
2.1 竞态条件:双十一抢购
生活比喻:最后一件商品,你和朋友同时点击"立即购买"
java
public class RaceConditionExample {
private static int inventory = 1; // 库存只有1件
public static void main(String[] args) throws InterruptedException {
System.out.println("🛒 开始抢购,库存: " + inventory);
Thread you = new Thread(() -> buy("你"));
Thread friend = new Thread(() -> buy("朋友"));
you.start();
friend.start();
you.join();
friend.join();
System.out.println("📦 最终库存: " + inventory);
}
static void buy(String buyer) {
if (inventory > 0) {
// 模拟网络延迟
try { Thread.sleep(100); } catch (InterruptedException e) {}
inventory--; // 库存减1
System.out.println("🎉 " + buyer + " 抢购成功!");
} else {
System.out.println("😞 " + buyer + " 抢购失败,库存不足");
}
}
}
可能的结果:
🛒 开始抢购,库存: 1
🎉 你 抢购成功!
🎉 朋友 抢购成功!
📦 最终库存: -1
问题:两个人都成功,但库存变负数!
2.2 解决方案:加锁,像超市收银台排队
java
public class SafeShopping {
private static int inventory = 1;
private static final Object lock = new Object(); // 创建一个锁对象
public static void main(String[] args) throws InterruptedException {
System.out.println("🛒 安全抢购开始,库存: " + inventory);
Thread you = new Thread(() -> safeBuy("你"));
Thread friend = new Thread(() -> safeBuy("朋友"));
you.start();
friend.start();
you.join();
friend.join();
System.out.println("📦 最终库存: " + inventory);
}
static void safeBuy(String buyer) {
synchronized (lock) { // 一次只让一个人进入这个区域
if (inventory > 0) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
inventory--;
System.out.println("🎉 " + buyer + " 抢购成功!");
} else {
System.out.println("😞 " + buyer + " 抢购失败,库存不足");
}
}
}
}
结果:
🛒 安全抢购开始,库存: 1
🎉 你 抢购成功!
😞 朋友 抢购失败,库存不足
📦 最终库存: 0
三、可见性问题:公告栏信息不同步
3.1 问题演示:经理发通知,员工看不到
java
public class VisibilityProblem {
private static boolean meetingCancelled = false; // 会议取消标志
public static void main(String[] args) throws InterruptedException {
// 员工线程:不断检查会议是否取消
Thread employee = new Thread(() -> {
System.out.println("👨💼 员工:等待会议开始...");
int checkCount = 0;
while (!meetingCancelled) {
checkCount++;
// 员工一直在检查...
}
System.out.println("👨💼 员工:收到通知,会议取消!检查了 " + checkCount + " 次");
});
// 经理线程:取消会议
Thread manager = new Thread(() -> {
try {
Thread.sleep(1000); // 经理思考1秒
meetingCancelled = true;
System.out.println("👩💼 经理:会议已取消!");
} catch (InterruptedException e) {}
});
employee.start();
manager.start();
employee.join();
manager.join();
}
}
可能的结果:员工线程永远看不到会议取消!
3.2 解决方案:用大喇叭广播(volatile)
java
public class VisibilitySolution {
private static volatile boolean meetingCancelled = false; // 加volatile
public static void main(String[] args) throws InterruptedException {
Thread employee = new Thread(() -> {
System.out.println("👨💼 员工:等待会议开始...");
int checkCount = 0;
while (!meetingCancelled) {
checkCount++;
}
System.out.println("👨💼 员工:收到通知,会议取消!检查了 " + checkCount + " 次");
});
Thread manager = new Thread(() -> {
try {
Thread.sleep(1000);
meetingCancelled = true;
System.out.println("👩💼 经理:会议已取消!");
} catch (InterruptedException e) {}
});
employee.start();
manager.start();
employee.join();
manager.join();
}
}
结果:员工能立即看到经理的通知
四、死锁:十字路口堵车
4.1 死锁产生
java
public class DeadlockExample {
private static final Object northSouthRoad = new Object(); // 南北向道路
private static final Object eastWestRoad = new Object(); // 东西向道路
public static void main(String[] args) {
// 南北方向的车
Thread northSouthCar = new Thread(() -> {
synchronized (northSouthRoad) {
System.out.println("🚗 南北车:占用了南北道路");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("🚗 南北车:等待东西道路...");
synchronized (eastWestRoad) {
System.out.println("🚗 南北车:通过十字路口");
}
}
});
// 东西方向的车
Thread eastWestCar = new Thread(() -> {
synchronized (eastWestRoad) {
System.out.println("🚙 东西车:占用了东西道路");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("🚙 东西车:等待南北道路...");
synchronized (northSouthRoad) {
System.out.println("🚙 东西车:通过十字路口");
}
}
});
northSouthCar.start();
eastWestCar.start();
}
}
结果:两辆车都卡住,谁也过不去!
4.2 死锁解决:统一方向规则
java
public class DeadlockSolution {
private static final Object roadA = new Object();
private static final Object roadB = new Object();
public static void main(String[] args) {
// 规定:必须先申请roadA,再申请roadB
Thread car1 = new Thread(() -> {
synchronized (roadA) {
System.out.println("🚗 车1:占用了道路A");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (roadB) {
System.out.println("🚗 车1:通过十字路口");
}
}
});
Thread car2 = new Thread(() -> {
synchronized (roadA) { // 同样先申请roadA
System.out.println("🚙 车2:占用了道路A");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (roadB) {
System.out.println("🚙 车2:通过十字路口");
}
}
});
car1.start();
car2.start();
}
}
五、synchronized 的三种用法
5.1 实例方法同步 - 锁住当前对象
java
public class BankAccount {
private int balance = 1000;
// 同步实例方法 - 锁是当前账户对象(this)
public synchronized void withdraw(String user, int amount) {
if (balance >= amount) {
System.out.println(user + " 开始取款 " + amount);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
balance -= amount;
System.out.println(user + " 取款成功,余额: " + balance);
} else {
System.out.println(user + " 余额不足");
}
}
}
5.2 静态方法同步 - 锁住整个类
java
public class Bank {
private static int totalMoney = 100000;
// 同步静态方法 - 锁是Bank.class
public static synchronized void audit() {
System.out.println("开始查账...");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("查账完成,总资金: " + totalMoney);
}
}
5.3 同步代码块 - 灵活控制
java
public class SmartBankAccount {
private int balance = 1000;
private final Object lock = new Object(); // 专门的锁对象
public void transfer(String from, String to, int amount) {
// 非同步操作
System.out.println(from + " 向 " + to + " 转账 " + amount);
// 只同步关键部分
synchronized (lock) {
if (balance >= amount) {
try { Thread.sleep(500); } catch (InterruptedException e) {}
balance -= amount;
System.out.println("转账成功,余额: " + balance);
} else {
System.out.println("余额不足,转账失败");
}
}
// 其他非同步操作
System.out.println("转账操作完成");
}
}
六、原子类:内置的"安全操作"
6.1 原子计数器
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0); // 原子整数
public void safeIncrement() {
int newValue = count.incrementAndGet(); // 原子自增
System.out.println(Thread.currentThread().getName() + " 增加后: " + newValue);
}
public static void main(String[] args) throws InterruptedException {
AtomicCounter counter = new AtomicCounter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.safeIncrement();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数: " + counter.count.get());
}
}
七、实战总结:并发编程最佳实践
7.1 简单的计数器对比
java
public class CounterComparison {
// 不安全计数器
private int unsafeCount = 0;
// 安全计数器 - synchronized
private int safeCount = 0;
// 安全计数器 - 原子类
private AtomicInteger atomicCount = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CounterComparison demo = new CounterComparison();
System.out.println("🔒 测试不安全计数器:");
demo.testUnsafeCounter();
System.out.println("\n🔒 测试安全计数器(synchronized):");
demo.testSafeCounter();
System.out.println("\n🔒 测试原子计数器:");
demo.testAtomicCounter();
}
void testUnsafeCounter() throws InterruptedException {
unsafeCount = 0;
Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) unsafeCount++; });
Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) unsafeCount++; });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("预期: 20000, 实际: " + unsafeCount);
}
void testSafeCounter() throws InterruptedException {
safeCount = 0;
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
safeCount++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
safeCount++;
}
}
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("预期: 20000, 实际: " + safeCount);
}
void testAtomicCounter() throws InterruptedException {
atomicCount.set(0);
Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) atomicCount.incrementAndGet(); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) atomicCount.incrementAndGet(); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("预期: 20000, 实际: " + atomicCount.get());
}
}
八、记住这些要点
- 并发就像单人杂技:快速切换,看起来同时
- 并行就像团队协作:真正同时工作
- 锁就像会议室:一次只进一个人
- volatile就像大喇叭:一有变化通知所有人
- 原子类就像自动售货机:内置安全机制
黄金法则:先保证正确性,再考虑性能。没有正确的并发,再快的速度也是徒劳!
通过这些生活化的比喻和实际的代码示例,相信你对并发编程有了更直观的理解。记住,并发编程的本质就是:在共享的环境中维持秩序!