Java 并发编程完整讲解
一、并发基础概念
1. 并发 vs 并行
- 并发:多个任务交替执行,单核 CPU 切换线程,宏观同时跑,微观串行
- 并行:多核 CPU 同时执行多个线程,真正同一时刻运行
2. 进程 & 线程
- 进程:操作系统资源分配最小单位,独立内存、文件句柄,进程间隔离
- 线程:进程内执行单元,共享进程堆内存,栈独立,切换开销远小于进程
- JVM:一个 Java 程序启动就是一个进程,默认主线程
main
3. 三大并发核心问题
- 可见性:一个线程修改共享变量,其他线程看不到
- 原子性:多步操作不可拆分,中途不能被其他线程打断(i++ 非原子)
- 有序性:CPU / 编译器指令重排,代码执行顺序和书写不一致
4. JMM Java 内存模型
JMM 规定线程、主内存、工作内存交互规则:
- 所有共享变量存在主内存
- 每个线程私有工作内存(缓存寄存器)
- 线程读写变量必须拷贝到工作内存,不能直接操作主存 三大关键字对应三大特性:
volatile:保证可见性 + 禁止指令重排,不保证原子性synchronized:保证原子性 + 可见性 + 有序性Lock锁:和 synchronized 语义一致,API 层面锁
二、Java 创建线程的 4 种方式
1. 继承 Thread 类
java
class MyThread extends Thread { @Override public void run() { System.out.println("线程执行"); } } // 使用 new MyThread().start();缺点:单继承限制,无法再继承其他类
2. 实现 Runnable 接口
无返回值,无异常抛出,解耦线程与任务
java
Runnable task = () -> System.out.println("任务"); new Thread(task).start();3. Callable + FutureTask
可抛出异常,获取线程执行结果
java
Callable<Integer> call = () -> 100; FutureTask<Integer> ft = new FutureTask<>(call); new Thread(ft).start(); Integer res = ft.get(); // 阻塞获取结果4. 线程池 ExecutorService
手动创建线程开销大、不可控,统一线程池管理
java
ExecutorService pool = Executors.newFixedThreadPool(5); pool.submit(() -> {}); pool.shutdown();线程核心方法
start():创建内核线程,调用 run (),只能调用一次run():普通方法,直接调用不会新开线程sleep(long):休眠,释放 CPU,不释放锁wait():Object 方法,释放锁,进入等待队列,需 notify 唤醒notify()/notifyAll():唤醒等待线程join():主线程等待子线程执行完毕interrupt():中断线程,设置中断标记,不直接停止
三、锁机制
1. synchronized 内置锁(悲观锁)
使用场景
- 修饰实例方法:锁当前对象
this - 修饰静态方法:锁类 Class 对象
- 修饰代码块:自定义锁对象
java
// 实例锁 public synchronized void test(){} // 代码块锁 synchronized (obj) {} // 静态锁 public static synchronized void test(){}底层原理
- 字节码:
monitorenter/monitorexit - 对象头 Mark Word 存储锁标记:无锁→偏向锁→轻量级锁→重量级锁(锁膨胀)
- 自动释放锁,异常也会释放,无需手动解锁
优缺点
优点:语法简单、自动释放、JVM 优化 缺点:阻塞不可中断、非公平锁、无法灵活控制
2. Lock 显式锁(java.util.concurrent.locks)
ReentrantLock 可重入锁
java
Lock lock = new ReentrantLock(); lock.lock(); try { // 临界区 } finally { lock.unlock(); // 必须finally释放,防止死锁 }相比 synchronized 优势:
- 可中断等待:
lockInterruptibly() - 尝试非阻塞获取锁:
tryLock() - 公平锁 / 非公平锁可选:
new ReentrantLock(true)公平 - 多条件等待:多个 Condition 区分等待队列
ReadWriteLock 读写锁
- ReentrantReadWriteLock:读共享、写互斥
- 读多写少场景(缓存)提升并发;写锁阻塞所有读写
StampedLock 乐观读写锁
提供乐观读模式,无锁开销,适合高并发读场景
3. 锁分类
- 可重入锁:同一线程多次获取同一锁不会死锁(synchronized、ReentrantLock 都是)
- 公平锁:线程按等待顺序获取锁;非公平锁抢占,吞吐量更高
- 悲观锁:默认一定会竞争,加锁阻塞(synchronized、Lock)
- 乐观锁:无锁,版本号 CAS 判断冲突(Atomic、数据库 MVCC)
- 自旋锁:线程循环尝试获取锁,不挂起,短等待场景高效
四、volatile 关键字
作用
- 可见性:修改后强制刷新主存,其他线程缓存失效,立即读取主存
- 禁止指令重排:插入内存屏障,保证执行顺序
局限性
不保证原子性,volatile int i; i++线程不安全
典型使用场景
- 状态标记(开关变量)
java
volatile boolean stop = false; while(!stop){}- 双重检查锁 DCL 单例(防止指令重排导致半初始化对象)
java
public class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) instance = new Singleton(); } } return instance; } }五、CAS & 原子类(无锁并发)
CAS 比较并交换
乐观锁核心算法:V(内存值) == E(预期值) ? 更新为N : 失败重试CPU 硬件指令保证原子操作,无锁、无内核切换,性能高 问题:
- ABA 问题:值 A→B→A,CAS 无法感知变化 → 用
AtomicStampedReference版本号解决 - 循环自旋消耗 CPU
- 只能单个变量原子操作
JUC 原子工具包 java.util.concurrent.atomic
- 基本类型:
AtomicInteger、AtomicLong、AtomicBoolean
jav运行
AtomicInteger num = new AtomicInteger(0); num.incrementAndGet(); // i++原子操作- 引用类型:
AtomicReference、AtomicStampedReference(解决 ABA) - 数组原子类:
AtomicIntegerArray - 对象字段更新器:
AtomicIntegerFieldUpdater(节省对象内存) - 累加器:
LongAdder高并发下优于 AtomicLong,分段计数降低 CAS 竞争
六、线程间通信
1. wait / notify / notifyAll(Object)
前提:必须在 synchronized 同步块内
- wait ():释放锁,阻塞等待,等待其他线程 notify
- notify ():随机唤醒一个等待线程
- notifyAll ():唤醒全部等待线程 标准生产者消费者模型基础
2. Condition(Lock 配套)
替代 wait/notify,支持多等待队列,精准唤醒指定线程
java
Condition full = lock.newCondition(); Condition empty = lock.newCondition(); empty.await(); // 等价wait full.signal(); // 等价notify3. CountDownLatch 减法计数器
等待 N 个线程完成后主线程再执行
java
CountDownLatch latch = new CountDownLatch(5); latch.countDown(); // 计数-1 latch.await(); // 阻塞直到计数04. CyclicBarrier 循环屏障
多个线程互相等待,全部到达屏障点再一起执行,可重复使用
java
CyclicBarrier barrier = new CyclicBarrier(3); barrier.await(); // 3个线程都到这里才放行5. Semaphore 信号量
控制并发线程数量,限流工具
java
Semaphore sem = new Semaphore(5); // 最多5个并发 sem.acquire(); // 获取许可 sem.release(); // 释放许可七、线程池
1. 为什么要用线程池
- 避免频繁创建销毁线程,降低开销
- 控制并发数量,防止 CPU 耗尽
- 统一管理线程,提供定时、延迟、任务拒绝策略
2. ThreadPoolExecutor 七大参数
java
public ThreadPoolExecutor( int corePoolSize, // 核心线程数(常驻) int maximumPoolSize, // 最大线程数 long keepAliveTime, // 非核心线程空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 阻塞队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )执行流程:
- 任务进来,核心线程未满 → 创建核心线程执行
- 核心线程满,任务放入阻塞队列
- 队列满,创建非核心线程直到达到最大线程数
- 总线程达上限,执行拒绝策略
3. 4 种拒绝策略
- AbortPolicy(默认):直接抛 RejectedExecutionException
- DiscardPolicy:丢弃当前任务,无异常
- DiscardOldestPolicy:丢弃队列最老任务,重试提交
- CallerRunsPolicy:交给提交任务主线程执行,限流
4. 内置工具类线程池
newFixedThreadPool(n):固定线程,无界队列,任务堆积 OOMnewSingleThreadExecutor():单线程,无界队列newCachedThreadPool():可缓存,无核心线程,最大线程无限,线程暴涨 OOMnewScheduledThreadPool():定时 / 延迟任务线程池 生产规范:手动 new ThreadPoolExecutor,自定义队列、线程数、拒绝策略
5. ScheduledExecutorService 定时任务
替代 Timer(单线程,任务失败全部阻塞),多线程定时执行
八、并发容器 JUC 集合
1. 阻塞队列 BlockingQueue
生产者消费者核心,队列满 / 空自动阻塞
- ArrayBlockingQueue:数组有界阻塞队列,一把锁
- LinkedBlockingQueue:链表无界 / 有界,读写分离两把锁,并发高
- SynchronousQueue:无容量,插入必须等待取出
- PriorityBlockingQueue:优先级阻塞队列
- DelayQueue:延迟队列,定时任务底层
2. 并发 Map
HashMap:线程不安全,并发 put 会链表循环死循环Hashtable:全表 synchronized,性能极差ConcurrentHashMap(推荐):分段锁 / 数组 CAS+synchronized,高并发安全- JDK7:分段 Segment 锁
- JDK8:取消分段,数组节点 CAS,链表转红黑树,锁只锁头节点
3. 其他并发集合
- CopyOnWriteArrayList/CopyOnWriteArraySet:写时复制,读无锁,写加锁复制新数组,读多写少场景
九、并发常见问题
1. 死锁
四个必要条件
- 互斥:资源独占
- 持有并等待:持有 A,等待 B
- 不可剥夺:资源不能强制抢走
- 循环等待:线程 1 等线程 2 资源,线程 2 等线程 1 资源
解决死锁:破坏任一条件,统一锁获取顺序、设置超时、避免嵌套锁
2. 线程安全总结
不安全场景:多线程读写共享变量,无同步措施 安全方案优先级:
- 无共享(栈局部变量)
- 不可变对象 final
- 原子类 CAS
- Lock 锁
- synchronized
十、并发工具完整使用场景总结
表格
| 工具 | 用途 |
|---|---|
| volatile | 状态标记、DCL 单例 |
| synchronized | 简单同步、少量竞争 |
| ReentrantLock | 公平锁、可中断、多条件 |
| ReadWriteLock | 读多写少缓存 |
| AtomicXXX | 单个变量原子计数 |
| LongAdder | 高并发统计 |
| CountDownLatch | 等待多线程完成 |
| CyclicBarrier | 多线程同步等待 |
| Semaphore | 接口限流 |
| ThreadPoolExecutor | 异步任务执行 |
| ConcurrentHashMap | 高并发键值存储 |
| BlockingQueue | 生产者消费者 |
十一、并发编程最佳实践
- 尽量缩小同步代码块范围,降低锁持有时间
- 优先使用局部变量、不可变对象,避免共享
- 高并发计数用 LongAdder 代替 AtomicLong
- 生产禁止 Executors 快捷创建线程池,手动定义 ThreadPoolExecutor
- Lock 锁必须放在 try-finally 中释放
- 双重检查单例必须加 volatile
- 避免嵌套锁,防止死锁
- 线程池合理设置核心线程、队列长度,自定义拒绝策略
- 读多写少场景使用 CopyOnWrite、读写锁
- 不依赖线程优先级、yield,调度不可控
