一、并发编程基础概念
1.1 并发与并行
- 并发:多任务交替执行(单核/多核),依赖 CPU 时间片轮转。宏观上是同时进行的,微观上是串行。
- 并行:多任务同时执行(必须多核),利用多个 CPU 核心同时处理不同任务。
1.2 Java 内存模型 (JMM)
- 主内存:所有线程共享,存储实例变量、静态变量。
- 工作内存:每个线程私有,存储主内存变量的副本。线程不能直接操作主内存,必须先读入工作内存,操作完成后再写回。
JMM 定义的 8 种内存交互操作(原子指令):
- lock(锁定):作用于主内存,将变量标识为线程独占。
- unlock(解锁):作用于主内存,释放独占状态。
- read(读取):作用于主内存,将变量值传输到工作内存。
- load(载入):作用于工作内存,将 read 的值放入变量副本。
- use(使用):作用于工作内存,将变量值传递给执行引擎。
- assign(赋值):作用于工作内存,将执行引擎的值赋给变量副本。
- store(存储):作用于工作内存,将变量值传送到主内存。
- write(写入):作用于主内存,将 store 的值写入主内存变量。
1.3 线程安全问题的根源与表现
- 根源:主内存与工作内存数据不一致。
- 竞态条件:结果依赖线程执行的相对时序(如
check-then-act)。 - 数据竞争:多线程访问同一内存,至少一个写操作,且无同步。
- 死锁:两个及以上线程互相持有对方需要的锁,永久阻塞。
二、并发编程三大特性(详细版)
| 特性 | 详细描述 | 实现手段 |
|---|---|---|
| 可见性 | 线程 A 修改变量后,线程 B 能立即看到变化。 | volatile、synchronized、Lock(锁释放前刷新主存)、final |
| 原子性 | 一个或多个操作要么全执行,要么全不执行,中间不被调度中断。 | synchronized、Lock、java.util.concurrent.atomic 原子类 |
| 有序性 | 避免指令重排序带来的逻辑错误。单线程内语义表现为串行。 | volatile(内存屏障)、synchronized(锁释放与获取原则) |
三、线程基础 API 详解
3.1 创建线程的方式(含代码细节)
方式一:继承 Thread 类
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
ThreadExample t = new ThreadExample();
t.start();
}
}方式二:实现 Runnable 接口
public class RunnableExample implements Runnable {
@Override
public void run() {
System.out.println("Runnable task is running");
}
public static void main(String[] args) {
Thread t = new Thread(new RunnableExample());
t.start();
// Lambda 简化写法
new Thread(() -> System.out.println("Lambda style")).start();
}
}方式三:实现 Callable 接口(有返回值、可抛异常)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableExample implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Callable result";
}
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<>(new CallableExample());
new Thread(futureTask).start();
// 阻塞获取结果
System.out.println(futureTask.get());
}
}3.2 Thread 类构造方法与核心控制
构造方法细节:
Thread():默认线程名"Thread-" + n。Thread(String name):指定线程名。Thread(Runnable target):指定任务。Thread(Runnable target, String name):指定任务与名称。
线程优先级与守护线程:
Thread t = new Thread();
t.setPriority(Thread.MAX_PRIORITY); // 10(最高)
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)
t.setPriority(Thread.MIN_PRIORITY); // 1(最低)
// 守护线程:JVM 退出时不管守护线程是否结束
Thread daemonT = new Thread(() -> {
while (true) { /* 后台任务,如日志清理 */ }
});
daemonT.setDaemon(true); // 必须在 start() 前设置
daemonT.start();3.3 线程中断机制(方法签名详解)
void interrupt():设置中断标志位为true。boolean isInterrupted():检查标志位,不清除。static boolean interrupted():检查当前线程标志位,并清除(重置为false)。
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 业务逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 捕获异常后标志位会被清除,需手动再次中断以退出循环
Thread.currentThread().interrupt();
}
}
});四、synchronized 锁机制深度解析
4.1 锁的三种形态(代码示例)
对象锁(锁当前实例 this):
class SyncDemo {
// 同步实例方法
public synchronized void method1() { /* 锁 this */ }
// 同步代码块
public void method2() {
synchronized (this) { /* 锁 this */ }
}
}类锁(锁 Class 对象):
class SyncDemo {
// 同步静态方法
public static synchronized void staticMethod1() { /* 锁 SyncDemo.class */ }
public void staticMethod2() {
synchronized (SyncDemo.class) { /* 锁类对象 */ }
}
}4.2 锁升级机制(Java 6+,不可逆)
- 偏向锁:无竞争时,在对象头记录线程 ID,线程再次进入仅需检查 ID,无需 CAS。
- 轻量级锁:交替执行时,通过 CAS 自旋尝试获取锁,不阻塞线程(消耗 CPU)。
- 重量级锁:激烈竞争时,自旋失败升级为 OS 互斥量,线程挂起进入阻塞队列(开销大)。
五、volatile 与内存屏障
5.1 volatile 特性总结
- 保证可见性:写 volatile 变量时强制刷新工作内存到主内存;读 volatile 变量时强制从主内存加载。
- 禁止重排序:通过插入内存屏障防止 CPU/编译器对指令进行重排。
- 不保证原子性:
i++操作(读-改-写)依然不安全。
5.2 内存屏障分类(JMM 规范)
| 屏障类型 | 作用 | 示例场景 |
|---|---|---|
| LoadLoad | 屏障前读操作完成,才能执行屏障后读操作。 | 读变量A; LoadLoad; 读变量B; |
| LoadStore | 屏障前读操作完成,才能执行屏障后写操作。 | 读变量A; LoadStore; 写变量B; |
| StoreStore | 屏障前写操作完成并可见,才能执行屏障后写操作。 | 写变量A; StoreStore; 写变量B; |
| StoreLoad | 屏障前写操作完成并可见,才能执行屏障后读操作。开销最大,兼具其他三者功能。 | 写变量A; StoreLoad; 读变量B; |
volatile 屏障插入策略:
- volatile 写:前插 StoreStore,后插 StoreLoad。
- volatile 读:后插 LoadLoad + LoadStore。
六、Lock 接口与 ReentrantLock 详细 API
6.1 Lock 接口方法清单
| 方法 | 详细描述 |
|---|---|
void lock() | 阻塞获取锁,不可中断,死等。 |
void lockInterruptibly() | 可响应中断的获取锁,等待时若线程被中断则抛 InterruptedException。 |
boolean tryLock() | 非阻塞,获取到返回 true,否则立即返回 false。 |
boolean tryLock(long time, TimeUnit unit) | 超时等待,超时返回 false;等待时可响应中断。 |
void unlock() | 释放锁。必须在 finally 块中调用。 |
Condition newCondition() | 返回绑定此锁的条件变量。 |
6.2 ReentrantLock 使用模板
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock(); // 关键:防止死锁
}
}
}6.3 公平锁与非公平锁构造
// 非公平锁(默认,吞吐量高)
ReentrantLock unfairLock = new ReentrantLock();
ReentrantLock unfairLock2 = new ReentrantLock(false);
// 公平锁(FIFO,避免线程饥饿但上下文切换多)
ReentrantLock fairLock = new ReentrantLock(true);6.4 可中断与超时示例
// 可中断锁示例
try {
lock.lockInterruptibly();
// 临界区
} catch (InterruptedException e) {
// 线程被中断,退出等待队列
} finally {
lock.unlock();
}
// 超时锁示例
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 获取成功
} finally {
lock.unlock();
}
} else {
// 执行备选方案
}七、读写锁与 StampedLock
7.1 ReentrantReadWriteLock 特性
- 读写互斥:读锁与写锁互斥。
- 锁降级:持有写锁的线程可以获取读锁,然后释放写锁,降级为读锁。
- 不支持锁升级:持有读锁时直接获取写锁会导致死锁,JVM 禁止此行为。
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// 缓存更新场景:锁降级示例
writeLock.lock();
try {
// 1. 更新数据库/缓存
readLock.lock(); // 在释放写锁前获取读锁
} finally {
writeLock.unlock(); // 降级为读锁
}
try {
// 2. 后续读取操作(保证数据可见性)
} finally {
readLock.unlock();
}7.2 StampedLock 三种模式(Java 8+,不可重入)
- 写锁:
long stamp = stampedLock.writeLock();返回邮戳。 - 悲观读锁:
long stamp = stampedLock.readLock(); - 乐观读:无锁机制,依赖版本校验。
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
// 乐观读(读多写少性能最优)
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 获取乐观读邮戳
double currentX = x, currentY = y; // 无锁读取
if (!sl.validate(stamp)) { // 校验邮戳是否失效
stamp = sl.readLock(); // 失效则升级为悲观读
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}八、Condition 条件变量(替换 Object Monitor)
8.1 核心方法对比
| Condition 方法 | Object 对应方法 | 说明 |
|---|---|---|
await() | wait() | 释放锁并等待。必须在 while 循环中调用以防虚假唤醒。 |
signal() | notify() | 唤醒一个等待线程。 |
signalAll() | notifyAll() | 唤醒所有等待线程。 |
awaitUninterruptibly() | 无 | 不可中断等待。 |
awaitUntil(Date deadline) | wait(long timeout) | 带截止时间等待。 |
8.2 生产者-消费者模型(多条件队列)
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
// 防御性编程:必须用 while 循环
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) // 防止虚假唤醒
notFull.await();
// ... 入队逻辑
notEmpty.signal();
} finally {
lock.unlock();
}
}
}九、原子类与 CAS 底层原理
9.1 AtomicInteger 常用方法
| 方法签名 | 操作描述 |
|---|---|
int get() | 获取当前值。 |
void set(int newValue) | 设置新值(无原子性保证,仅可见性)。 |
int getAndSet(int newValue) | 设置新值,返回旧值。 |
boolean compareAndSet(expect, update) | CAS 核心操作。 |
int getAndIncrement() | 等价于 i++。 |
int incrementAndGet() | 等价于 ++i。 |
int getAndAdd(int delta) | 加 delta 并返回旧值。 |
9.2 ABA 问题与解决方案
ABA 问题描述:线程 1 读取值 A,线程 2 改为 B,线程 3 又改回 A。线程 1 使用 CAS 检查时发现值仍为 A,误以为未变化而更新成功。
解决方案:AtomicStampedReference 引入版本号。
// 泛型类:AtomicStampedReference<V>
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 0); // 初始值100,版本号0
int stamp = ref.getStamp();
int oldValue = ref.getReference();
// 更新时需同时匹配值和时间戳
boolean success = ref.compareAndSet(oldValue, 200, stamp, stamp + 1);
评论