Appearance
并发编程
程序员的进阶门槛:高并发时代,你准备好了吗? 🚀
🎯 场景:为什么需要并发?
┌─────────────────────────────────────────────────────────────────┐
│ 串行 vs 并行 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 🍜 串行:厨师一个人,炒完炒饭再炒菜 │
│ 客户点4个菜 → 等20分钟 → 全部上齐 │
│ │
│ 👨🍳 并行:4个厨师同时炒菜 │
│ 客户点4个菜 → 5分钟 → 全部上齐 │
│ │
│ 这就是并发的好处:充分利用 CPU,提高效率! │
│ │
└─────────────────────────────────────────────────────────────────┘💡 实战场景:
┌─────────────────────────────────────────────────────────────────┐
│ 高并发场景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 🌐 双十一抢购 │
│ 同一时刻上百万人下单 → 需要并发处理 │
│ │
│ 📱 抢红包 │
│ 群里有 500 人,每人每秒抢一次 → 高并发 │
│ │
│ 📊 数据统计 │
│ 报表系统需要在 1 分钟内统计千万条数据 → 并行计算 │
│ │
│ 🔍 搜索建议 │
│ 每个人每秒触发多次联想词查询 → 并发读取 │
│ │
└─────────────────────────────────────────────────────────────────┘并发编程要解决的问题:
- 如何让多个任务同时执行?
- 多个任务同时访问同一个资源怎么办?
- 如何避免线程安全问题?
并发基础
进程与线程
┌─────────────────────────────────────────────────────────────────┐
│ 进程 vs 线程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程 (Process) 线程 (Thread) │
│ ───────────── ───────────── │
│ 独立的内存空间 共享进程的内存空间 │
│ 操作系统分配资源的基本单位 CPU 调度的基本单位 │
│ 通信复杂 (IPC) 通信简单 (共享内存) │
│ 创建/销毁开销大 创建/销毁开销小 │
│ │
└─────────────────────────────────────────────────────────────────┘创建线程的几种方式
java
// 方式1:继承 Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承 Thread 创建线程");
}
}
new MyThread().start();
// 方式2:实现 Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现 Runnable 创建线程");
}
}
new Thread(new MyRunnable()).start();
// 方式3:实现 Callable + FutureTask (有返回值,可抛异常)
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 123;
}
}
FutureTask<Integer> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
Integer result = future.get(); // 获取返回值
// 方式4:线程池 (推荐)
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("线程池执行"));
executor.shutdown();线程状态
┌─────────────┐
│ NEW │ 创建,尚未启动
└──────┬──────┘
│
▼
┌─────────────┐
│ RUNNABLE │ 可运行,正在运行或等待CPU
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌───────────┐
│ BLOCKED │ │ WAITING │ │TIMED_WAITING│
│ 阻塞等待 │ │ 无限等待 │ │ 计时等待 │
│ 监视器锁 │ │ wait/join │ │ sleep/poll │
└─────┬─────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└──────────────┬┴──────────────┘
▼
┌─────────────┐
│ TERMINATED │ 线程执行完毕
└─────────────┘线程状态转换示例
java
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程状态: " + Thread.currentThread().getState());
// RUNNABLE
});
System.out.println("启动前: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后: " + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("等待中: " + thread.getState()); // RUNNABLE
thread.join();
System.out.println("结束后: " + thread.getState()); // TERMINATED
}
}synchronized 关键字
基本用法
java
// 1. 同步方法
public synchronized void method() {
// 同步代码
}
// 2. 同步代码块
public void method() {
synchronized (this) { // 对象锁
// 同步代码
}
}
// 3. 静态同步方法 (锁的是 Class 对象)
public static synchronized void staticMethod() {
// 同步代码
}
// 4. 同步类对象
public void method() {
synchronized (MyClass.class) { // 类锁
// 同步代码
}
}锁的原理
java
public class SynchronizedDemo {
// 对象锁示例
public synchronized void method1() {
// 相当于 synchronized(this)
System.out.println("同步方法1");
}
public void method2() {
synchronized (this) {
System.out.println("同步代码块");
}
}
// 类锁示例
public static synchronized void staticMethod1() {
// 相当于 synchronized(SynchronizedDemo.class)
System.out.println("静态同步方法");
}
public static void staticMethod2() {
synchronized (SynchronizedDemo.class) {
System.out.println("类锁代码块");
}
}
}💡 面试高频点:synchronized 锁的是什么?
┌─────────────────────────────────────────────────────────────────┐
│ synchronized 锁的对象 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 实例方法 → this 对象(当前实例) │
│ 静态方法 → Class 对象(MyClass.class) │
│ 代码块 → 括号中的对象 │
│ │
│ 所有 synchronized 实例方法共享同一把锁 │
│ 所有 synchronized 静态方法共享另一把锁 │
│ │
└─────────────────────────────────────────────────────────────────┘Lock 接口
ReentrantLock
java
public class LockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 同步代码
System.out.println("执行同步操作");
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
// 可重入示例
public synchronized void methodA() {
System.out.println("methodA");
methodB(); // 可重入,同一把锁
}
public synchronized void methodB() {
System.out.println("methodB");
}
}ReentrantLock vs synchronized
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 获取方式 | 自动获取/释放 | 手动获取/释放 |
| 尝试非阻塞获取 | 不支持 | tryLock() |
| 超时获取 | 不支持 | tryLock(timeout) |
| 中断等待 | 不支持 | lockInterruptibly() |
| 公平锁 | 不支持 | 可选公平/非公平 |
| 多条件Condition | 不支持 | newCondition() |
java
// ReentrantLock 高级特性
ReentrantLock lock = new ReentrantLock();
// 1. 尝试获取锁(非阻塞)
if (lock.tryLock()) {
try {
// 获取到锁
} finally {
lock.unlock();
}
} else {
// 获取锁失败
}
// 2. 超时获取锁
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 5秒内获取到锁
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
// 等待时被中断
}
// 3. 公平锁(先来先服务)
ReentrantLock fairLock = new ReentrantLock(true);线程通信
wait/notify/notifyAll
java
public class WaitNotifyDemo {
private final Object lock = new Object();
private boolean ready = false;
// 生产者
public void produce() {
synchronized (lock) {
ready = true;
lock.notify(); // 唤醒一个等待线程
// lock.notifyAll(); // 唤醒所有等待线程
}
}
// 消费者
public void consume() {
synchronized (lock) {
while (!ready) { // 必须用 while,防止虚假唤醒
try {
lock.wait(); // 释放锁并等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费
System.out.println("消费数据");
}
}
}💡 面试高频点:为什么 wait/notify 必须在同步代码块中调用?
原因:wait/notify 需要获取对象监视器锁,才能操作对象的等待队列
如果在同步块外调用,会抛出 IllegalMonitorStateExceptionCondition 接口
java
public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void awaitTest() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 等待
}
System.out.println("继续执行");
} finally {
lock.unlock();
}
}
public void signalTest() {
lock.lock();
try {
ready = true;
condition.signal(); // 唤醒一个
// condition.signalAll(); // 唤醒所有
} finally {
lock.unlock();
}
}
}线程池
线程池架构
┌─────────────────────────────────────────────────────────────────┐
│ 线程池架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Executor │
│ │ │
│ ExecutorService │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ThreadPoolExecutor ScheduledExecutor ForkJoinPool │
│ │
└─────────────────────────────────────────────────────────────────┘创建线程池
java
// 1. FixedThreadPool - 固定大小
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 2. CachedThreadPool - 可缓存,按需创建
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 3. SingleThreadExecutor - 单线程
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 4. ScheduledThreadPool - 定时任务
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
// 5. ForkJoinPool - 分治任务 (Java 8+)
ForkJoinPool forkJoinPool = ForkJoinPool.common();ThreadPoolExecutor
java
// 参数详解
ThreadPoolExecutor executor = new ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
);
// 拒绝策略
// 1. AbortPolicy - 抛出RejectedExecutionException (默认)
// 2. CallerRunsPolicy - 由调用者线程执行
// 3. DiscardPolicy - 丢弃任务
// 4. DiscardOldestPolicy - 丢弃队列最旧的任务💡 面试高频点:线程池执行流程?
┌─────────────────────────────────────────────────────────────────┐
│ 线程池执行流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 提交任务 │
│ ↓ │
│ 2. 核心线程数未满 → 创建核心线程执行 │
│ ↓ │
│ 3. 核心线程数已满 → 加入任务队列 │
│ ↓ │
│ 4. 队列已满 & 最大线程数未满 → 创建临时线程执行 │
│ ↓ │
│ 5. 队列已满 & 最大线程数已满 → 执行拒绝策略 │
│ │
└─────────────────────────────────────────────────────────────────┘线程池使用示例
java
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交 Runnable
executor.submit(() -> {
System.out.println("任务执行");
});
// 提交 Callable
Future<String> future = executor.submit(() -> "结果");
String result = future.get(); // 阻塞获取结果
// 关闭线程池
executor.shutdown(); // 不再接受新任务,等待已提交任务完成
// executor.shutdownNow(); // 尝试停止所有任务
// 等待所有任务完成
executor.awaitTermination(60, TimeUnit.SECONDS);JUC 并发工具类
CountDownLatch
java
// 等待多个线程完成后主线程继续
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("任务完成");
latch.countDown(); // 计数减1
}).start();
}
latch.await(); // 等待计数为0
System.out.println("所有任务完成,主线程继续");
}
}CyclicBarrier
java
// 多个线程互相等待,到齐后一起执行
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有人都到了,开始执行!");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("等待中...");
barrier.await(); // 等待其他线程
System.out.println("继续执行");
}).start();
}
}
}Semaphore
java
// 控制并发访问资源的线程数
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 同时只允许2个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可证
System.out.println("执行任务");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可证
}
}).start();
}
}
}Exchanger
java
// 两个线程交换数据
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data = exchanger.exchange("线程1的数据");
System.out.println("线程1收到: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
String data = exchanger.exchange("线程2的数据");
System.out.println("线程2收到: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}原子类
AtomicInteger 等
java
// 原子操作,无锁实现线程安全
public class AtomicDemo {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // 自增并返回
// counter.getAndIncrement(); // 返回后自增
// counter.decrementAndGet(); // 自减并返回
}
public int get() {
return counter.get();
}
}
// 常用方法
atomicInt.incrementAndGet(); // ++i
atomicInt.getAndIncrement(); // i++
atomicInt.addAndGet(5); // i += 5
atomicInt.compareAndSet(0, 1); // CAS 操作AtomicReference
java
// 原子引用,用于对象
public class AtomicRefDemo {
private AtomicReference<User> userRef = new AtomicReference<>();
public void update(User newUser) {
userRef.compareAndSet(
userRef.get(), // 期望值
newUser // 新值
);
}
}并发容器
ConcurrentHashMap
java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 常用操作
map.putIfAbsent("key", 1); // 不存在才插入
map.computeIfAbsent("key", k -> 1); // 不存在才计算插入
map.merge("key", 1, Integer::sum); // 合并操作
// 原子操作
map.getAndAdd("key", 10); // 原子加减
map.updateAndGet("key", v -> v * 2); // 原子更新
// 批量操作 (Java 8+)
map.forEach((k, v) -> System.out.println(k + ": " + v));CopyOnWriteArrayList
java
// 读写分离,写时复制
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a"); // 每次都复制整个数组
list.addIfAbsent("b"); // 不存在才添加BlockingQueue
java
// 阻塞队列,生产者-消费者模式
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
// 生产者
new Thread(() -> {
try {
queue.put(1); // 队列满则阻塞
queue.offer(1, 5, TimeUnit.SECONDS); // 超时返回false
} catch (InterruptedException e) { }
}).start();
// 消费者
new Thread(() -> {
try {
Integer item = queue.take(); // 队列空则阻塞
Integer item2 = queue.poll(5, TimeUnit.SECONDS); // 超时返回null
} catch (InterruptedException e) { }
}).start();总结
并发编程核心知识点:
| 知识点 | 面试频率 | 实战重要性 |
|---|---|---|
| 线程创建方式 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| synchronized 原理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| synchronized vs Lock | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| wait/notify 原理 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 线程池原理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| JUC 工具类 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 线程安全集合 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
⚠️ 易错点提醒:
wait()会释放锁,sleep()不会释放锁notify()随机唤醒一个,notifyAll()唤醒所有- 使用
wait()/notify()必须用while判断条件 - 线程池拒绝策略在队列满且线程数达到最大值时触发
volatile只能保证可见性,不能保证原子性