故事场景:餐厅服务系统
假设你经营一家餐厅,顾客们来用餐需要点餐、做菜、上菜。随着生意越来越好,你需要优化餐厅的服务流程,这就涉及到 Java 并发编程的各种概念。
1. 并发模型(Concurrency Model)
故事:
你最初采用 "单厨师" 模式:一个厨师同时处理所有订单,做完一道菜再做下一道。但顾客抱怨等待时间太长。
于是你改为 "多厨师" 模式:多个厨师同时做菜,大大提高了效率。
Java 类比:
-
单线程模型:所有任务按顺序执行(如单个厨师)。
-
多线程模型:多个任务同时执行(如多个厨师)。
-
并行 vs 并发:并行是真正的同时执行(多核 CPU),并发是交替执行(单核 CPU 切换线程)。
代码示例:
java
// 单线程模型
public class SingleThreadModel {
public static void main(String[] args) {
cook("宫保鸡丁");
cook("鱼香肉丝");
}
private static void cook(String dish) {
System.out.println("开始做:" + dish);
try {
Thread.sleep(1000); // 模拟做菜时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(dish + "做好了");
}
}
// 多线程模型
public class MultiThreadModel {
public static void main(String[] args) {
new Thread(() -> cook("宫保鸡丁")).start();
new Thread(() -> cook("鱼香肉丝")).start();
}
private static void cook(String dish) {
System.out.println("厨师开始做:" + dish);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(dish + "做好了");
}
}
使用场景:
- 单线程:任务顺序执行,如简单脚本。
- 多线程:IO 密集型任务(如网络请求、文件读写)、并行计算。
2. 锁(Lock)
故事:
餐厅只有一个搅拌机,两个厨师需要用它。如果同时抢,可能会弄坏机器。于是你规定:使用前必须先拿到 "搅拌机钥匙",用完再还。
Java 类比:
-
synchronized 关键字:内置锁,同一时间只有一个线程可以执行同步代码块。
-
ReentrantLock:显式锁,功能更强大(可中断、可定时、公平锁)。
代码示例:
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Blender {
private final Lock lock = new ReentrantLock();
public void useBlender(String chef) {
lock.lock(); // 获取锁
try {
System.out.println(chef + "拿到了搅拌机");
Thread.sleep(1000);
System.out.println(chef + "用完了搅拌机");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
Blender blender = new Blender();
new Thread(() -> blender.useBlender("厨师A")).start();
new Thread(() -> blender.useBlender("厨师B")).start();
}
}
使用场景:
- 多线程共享资源(如账户转账、计数器)。
- 保证原子性操作(如 i++)。
3. CAS(Compare-And-Swap)
故事:
餐厅有个 "今日特价菜" 牌子,服务员可以更新它。为避免冲突,每次更新时需要确认:"我看到的价格是 X,如果是,就改成 Y"。
Java 类比:
-
CAS 操作:原子性地比较并更新值。
-
Unsafe 类:Java 底层提供的 CAS 操作(如
compareAndSwapInt)。
代码示例:
java
import java.util.concurrent.atomic.AtomicInteger;
public class SpecialDish {
private AtomicInteger price = new AtomicInteger(100); // 初始价格100元
public void updatePrice(int expectedPrice, int newPrice) {
// CAS操作:如果当前价格是expectedPrice,则更新为newPrice
boolean success = price.compareAndSet(expectedPrice, newPrice);
System.out.println("更新价格:" + (success ? "成功" : "失败"));
}
public static void main(String[] args) {
SpecialDish dish = new SpecialDish();
new Thread(() -> dish.updatePrice(100, 90)).start(); // 尝试将100改为90
new Thread(() -> dish.updatePrice(100, 80)).start(); // 尝试将100改为80
}
}
使用场景:
- 高并发下的无锁算法(如 ConcurrentHashMap)。
- 替代重量级锁,提高性能。
4. 原子变量(Atomic Variables)
故事:
餐厅有个 "今日客流量" 计数器,多个服务员需要同时增加计数。你买了一个特殊计数器,它的 ++ 操作是原子性的,不会出错。
Java 类比:
-
AtomicInteger、AtomicLong等:基于 CAS 实现的原子变量。
代码示例:
java
import java.util.concurrent.atomic.AtomicInteger;
public class CustomerCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子性自增
}
public int getCount() {
return count.get();
}
public static void main(String[] args) throws InterruptedException {
CustomerCounter counter = new CustomerCounter();
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终客流量:" + counter.getCount()); // 输出10000,不会出错
}
}
使用场景:
- 计数器、序列号生成器。
- 替代
synchronized实现轻量级同步。
5. 线程池(ThreadPool)
故事:
餐厅生意太好,每次来顾客都临时招厨师,做完菜就解雇,成本太高。于是你雇了固定数量的厨师团队(线程池),顾客来了直接分配厨师,无需等待招聘。
Java 类比:
-
ExecutorService:线程池接口。
-
Executors:工具类,提供创建线程池的静态方法。
代码示例:
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RestaurantThreadPool {
public static void main(String[] args) {
// 创建固定大小的线程池(3个厨师)
ExecutorService pool = Executors.newFixedThreadPool(3);
// 模拟10个顾客订单
for (int i = 1; i <= 10; i++) {
final int orderId = i;
pool.submit(() -> {
System.out.println("厨师开始处理订单:" + orderId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单" + orderId + "处理完成");
});
}
pool.shutdown(); // 关闭线程池
}
}
使用场景:
- 大量短期任务(如 Web 服务器处理请求)。
- 控制并发线程数量,避免资源耗尽。
6. AQS(AbstractQueuedSynchronizer)
故事:
餐厅的 "VIP 包间" 只有一个,顾客需要排队等待。你设计了一个排队系统:
-
顾客到达后先尝试进入包间(CAS 操作)。
-
如果包间被占用,顾客进入等待队列(双向链表)。
-
包间释放时,通知队列中的下一位顾客。
Java 类比:
-
AQS:Java 并发包的基础框架,提供锁和同步器的实现模板。
-
ReentrantLock、CountDownLatch等都基于 AQS 实现。
代码示例:
java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
// 自定义同步器(模拟VIP包间)
class VipRoomSync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) { // CAS操作:0表示空闲,1表示已占用
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0); // 释放资源
return true;
}
}
public class VipRoom {
private final VipRoomSync sync = new VipRoomSync();
public void enter() {
sync.acquire(1);
System.out.println(Thread.currentThread().getName() + "进入VIP包间");
}
public void leave() {
System.out.println(Thread.currentThread().getName() + "离开VIP包间");
sync.release(1);
}
public static void main(String[] args) {
VipRoom room = new VipRoom();
for (int i = 1; i <= 5; i++) {
final int customerId = i;
new Thread(() -> {
room.enter();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
room.leave();
}, "顾客" + customerId).start();
}
}
}
使用场景:
- 自定义同步器(如读写锁、信号量)。
- 实现复杂的并发控制逻辑。
总结
| 概念 | 故事类比 | Java 实现 | 使用场景 |
|---|---|---|---|
| 并发模型 | 单厨师 vs 多厨师 | 单线程 vs 多线程 | 任务顺序执行 vs 并行处理 |
| 锁 | 搅拌机钥匙 | synchronized, ReentrantLock | 共享资源保护 |
| CAS | 特价菜更新确认 | Unsafe.compareAndSwapInt | 无锁算法、高性能计数器 |
| 原子变量 | 特殊计数器 | AtomicInteger, AtomicLong | 计数器、序列号生成器 |
| 线程池 | 固定厨师团队 | ExecutorService | 大量短期任务、控制并发数 |
| AQS | VIP 包间排队系统 | AbstractQueuedSynchronizer | 自定义同步器、复杂并发控制 |
通过餐厅的例子,你可以把 Java 并发编程的各种概念想象成餐厅管理的策略,每种策略都解决了特定的效率或正确性问题。