1. JUC并发编程核心概念解析
在Java并发编程领域,JUC(java.util.concurrent)包提供了强大的工具集来应对多线程挑战。理解这些核心概念是构建高并发应用的基础。
1.1 管程与共享问题
管程(Monitor)是一种高级同步机制,它封装了共享变量和对这些变量的操作过程。在Java中,synchronized关键字就是基于管程的实现。当多个线程访问共享资源时,会出现三类典型问题:
- 竞态条件:多个线程对共享资源的操作顺序影响最终结果
- 内存可见性:一个线程对共享变量的修改可能不会立即对其他线程可见
- 指令重排序:编译器和处理器可能改变指令执行顺序
java复制// 典型竞态条件示例
public class RaceConditionDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) counter++;
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) counter--;
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final counter value: " + counter);
}
}
这段代码的输出结果通常不会是0,因为counter++和counter--操作不是原子性的。
1.2 原子性与可见性
原子性指一个操作不可中断,要么全部执行成功,要么完全不执行。在Java中,基本类型的读写(除long和double外)是原子性的,但像i++这样的复合操作不是。
可见性指一个线程对共享变量的修改能够及时被其他线程看到。由于CPU缓存的存在,线程可能读取到过期的数据。volatile关键字可以保证变量的可见性,但不保证原子性。
关键区别:synchronized既保证原子性又保证可见性,而volatile只保证可见性。选择时需要考虑性能与需求的平衡。
2. synchronized深度解析
2.1 synchronized实现原理
synchronized在JVM中的实现基于对象头中的Mark Word和Monitor机制。每个Java对象都可以作为锁,其锁状态记录在对象头的Mark Word中。
java复制// synchronized使用示例
public class SynchronizedDemo {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized(lock) { // 获取lock对象的Monitor
count++; // 临界区代码
} // 释放锁
}
}
synchronized的工作流程:
- 线程尝试通过CAS操作获取锁
- 成功则进入临界区执行代码
- 失败则进入阻塞队列等待
- 持有锁的线程执行完毕后唤醒等待线程
2.2 锁升级过程
Java中的锁有四种状态,会根据竞争情况自动升级:
- 无锁状态:新创建的对象
- 偏向锁:只有一个线程访问时启用,通过CAS设置线程ID
- 轻量级锁:有少量竞争时启用,通过CAS和自旋尝试获取锁
- 重量级锁:竞争激烈时启用,线程进入阻塞状态
锁升级是不可逆的过程,目的是在保证线程安全的同时减少同步开销。
2.3 锁的优化技巧
- 减小锁粒度:只锁定必要的代码块
- 锁分离:读写锁分离(ReadWriteLock)
- 锁消除:JVM对不可能存在竞争的锁进行消除
- 锁粗化:对连续多个小锁合并为一个大锁
java复制// 锁粒度优化示例
public class LockGranularity {
private final Map<String, String> map = new HashMap<>();
private final Object mapLock = new Object();
// 不推荐的粗粒度锁
public void updateAllEntries() {
synchronized(this) { // 锁定整个对象
// 更新所有条目
}
}
// 推荐的细粒度锁
public void updateEntry(String key, String value) {
synchronized(mapLock) { // 只锁定map相关操作
map.put(key, value);
}
}
}
3. volatile与内存模型
3.1 JMM内存模型
Java内存模型(JMM)定义了线程如何与内存交互,主要关注三个特性:
- 原子性:基本类型读写(除long/double)是原子的
- 可见性:volatile保证写操作对后续读可见
- 有序性:volatile禁止指令重排序
java复制// volatile使用示例
public class VolatileDemo {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
// 保证能看到writer()的修改
}
}
}
3.2 volatile实现原理
volatile通过内存屏障(Memory Barrier)实现其语义:
- 写屏障:确保volatile写之前的操作不会被重排序到写之后
- 读屏障:确保volatile读之后的操作不会被重排序到读之前
内存屏障类型:
- LoadLoad屏障
- StoreStore屏障
- LoadStore屏障
- StoreLoad屏障
实际经验:volatile适用于状态标志位(如开关控制),但不适合计数器等需要原子性的场景。
4. CAS与原子类
4.1 CAS原理
比较并交换(Compare And Swap)是乐观锁的核心算法,包含三个操作数:
- 内存位置(V)
- 预期原值(A)
- 新值(B)
CAS操作流程:
- 读取内存值V
- 比较V与A
- 相等则用B更新V
- 返回操作是否成功
java复制// 模拟CAS操作
public class SimulatedCAS {
private volatile int value;
public synchronized boolean compareAndSet(int expected, int newValue) {
if (value == expected) {
value = newValue;
return true;
}
return false;
}
}
4.2 原子类使用
Java提供了多种原子类:
- AtomicInteger/AtomicLong
- AtomicReference
- AtomicIntegerArray等
java复制// 原子类使用示例
public class AtomicDemo {
private AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
counter.incrementAndGet(); // 原子自增
}
public int getValue() {
return counter.get();
}
}
原子类内部通常使用Unsafe类实现CAS操作,适用于计数器、累加器等场景。
4.3 ABA问题及解决方案
ABA问题指变量从A变为B又变回A,CAS会误认为没有变化。解决方案:
- 版本号:AtomicStampedReference
- 标记位:AtomicMarkableReference
java复制// 解决ABA问题示例
public class ABADemo {
private AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(0, 0);
public void update(int expectedRef, int newRef,
int expectedStamp, int newStamp) {
ref.compareAndSet(expectedRef, newRef,
expectedStamp, newStamp);
}
}
5. AQS与显式锁
5.1 AQS核心原理
AbstractQueuedSynchronizer是JUC锁的基础框架,核心组件:
- state:同步状态,volatile修饰
- CLH队列:双向链表结构的等待队列
AQS支持两种模式:
- 独占模式:如ReentrantLock
- 共享模式:如Semaphore
java复制// 自定义同步器示例
class SimpleLock extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
}
5.2 ReentrantLock实现
ReentrantLock基于AQS实现,特点:
- 可重入:同一线程可多次获取锁
- 公平/非公平选择
- 支持条件变量
java复制// ReentrantLock使用示例
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 可替换为tryLock()等变体
try {
count++;
} finally {
lock.unlock();
}
}
}
5.3 读写锁优化
ReadWriteLock适用于读多写少场景,提高并发性:
- 读锁:共享锁,允许多线程同时读
- 写锁:独占锁,互斥其他所有操作
java复制// 读写锁使用示例
public class CacheDemo {
private final Map<String, Object> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object get(String key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
6. 线程池深度解析
6.1 线程池核心参数
ThreadPoolExecutor构造参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
java复制// 自定义线程池示例
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 空闲时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), // 任务队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
6.2 任务执行流程
- 提交任务到线程池
- 核心线程未满则创建新线程执行
- 核心线程已满则加入任务队列
- 队列已满且线程未达最大值则创建救急线程
- 达到最大线程数则执行拒绝策略
6.3 常见线程池类型
- FixedThreadPool:固定大小线程池
- CachedThreadPool:弹性线程池
- SingleThreadExecutor:单线程池
- ScheduledThreadPool:定时任务线程池
实际经验:阿里巴巴开发规范建议手动创建线程池,避免使用Executors工厂方法,以便更精确控制参数。
7. 并发编程实践技巧
7.1 避免死锁的方法
- 顺序加锁:所有线程按相同顺序获取锁
- 超时放弃:使用tryLock设置超时时间
- 死锁检测:定期检查锁依赖关系
java复制// 顺序加锁示例
public class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized(lock1) { // 先获取lock1
synchronized(lock2) {
// 操作共享资源
}
}
}
public void method2() {
synchronized(lock1) { // 同样先获取lock1
synchronized(lock2) {
// 操作共享资源
}
}
}
}
7.2 性能优化建议
- 减少锁持有时间
- 降低锁粒度
- 使用读写锁替代独占锁
- 考虑无锁数据结构
- 合理设置线程池参数
7.3 常见问题排查
- 线程转储分析:jstack获取线程快照
- 内存分析:MAT分析内存泄漏
- 性能监控:JVisualVM监控线程状态
bash复制# 获取线程转储命令
jstack <pid> > thread_dump.txt
8. JUC高级特性
8.1 Fork/Join框架
适用于可分治任务的并行计算框架,核心类:
- ForkJoinPool:特殊线程池
- ForkJoinTask:可分解任务
- RecursiveAction:无返回值任务
- RecursiveTask:有返回值任务
java复制// Fork/Join示例:计算1-n的和
class SumTask extends RecursiveTask<Long> {
private final int start;
private final int end;
SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start < 1000) { // 小任务直接计算
long sum = 0;
for (int i = start; i <= end; i++) sum += i;
return sum;
} else { // 大任务拆分
int mid = (start + end) / 2;
SumTask left = new SumTask(start, mid);
SumTask right = new SumTask(mid+1, end);
left.fork(); // 异步执行子任务
return right.compute() + left.join(); // 等待并合并结果
}
}
}
8.2 CompletableFuture
Java8引入的异步编程工具,支持:
- 链式调用
- 异常处理
- 组合多个Future
- 回调函数
java复制// CompletableFuture示例
public class AsyncDemo {
public CompletableFuture<String> fetchData() {
return CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try { Thread.sleep(1000); }
catch (InterruptedException e) { /* 处理中断 */ }
return "Data";
});
}
public void process() {
fetchData()
.thenApply(data -> data + " processed")
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
}
}
8.3 并发集合类
JUC提供的线程安全集合:
- ConcurrentHashMap:分段锁实现的HashMap
- CopyOnWriteArrayList:写时复制List
- BlockingQueue:阻塞队列接口
- ConcurrentLinkedQueue:无界非阻塞队列
java复制// ConcurrentHashMap使用示例
public class CacheSystem {
private final ConcurrentHashMap<String, Object> cache =
new ConcurrentHashMap<>();
public Object get(String key) {
return cache.get(key);
}
public void put(String key, Object value) {
cache.put(key, value);
}
// 原子操作示例
public Object atomicUpdate(String key) {
return cache.compute(key, (k, v) -> {
// 原子更新逻辑
return v == null ? new Object() : modify(v);
});
}
}
9. 并发设计模式
9.1 生产者-消费者模式
使用BlockingQueue简化实现:
java复制public class ProducerConsumer {
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>(10);
class Producer implements Runnable {
public void run() {
while (true) {
Task task = produceTask();
try {
queue.put(task); // 队列满时阻塞
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
class Consumer implements Runnable {
public void run() {
while (!Thread.interrupted()) {
try {
Task task = queue.take(); // 队列空时阻塞
processTask(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
9.2 线程本地存储模式
ThreadLocal为每个线程提供独立变量副本:
java复制public class UserContextHolder {
private static final ThreadLocal<User> context = new ThreadLocal<>();
public static void setUser(User user) {
context.set(user);
}
public static User getUser() {
return context.get();
}
public static void clear() {
context.remove(); // 防止内存泄漏
}
}
// 使用示例
public void processRequest(Request req) {
try {
UserContextHolder.setUser(req.getUser());
// 业务处理...
} finally {
UserContextHolder.clear(); // 必须清理
}
}
9.3 工作窃取模式
ForkJoinPool采用工作窃取算法提高CPU利用率:
- 每个线程维护自己的任务队列
- 空闲线程可以从其他线程队列尾部"窃取"任务
- 减少线程竞争,提高并行度
java复制// 工作窃取示例
public class WorkStealingDemo {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
pool.submit(() -> {
// 可分解任务...
});
pool.shutdown();
}
}
10. 并发调试与测试
10.1 多线程调试技巧
- 条件断点:设置线程特定的断点条件
- 线程转储分析:jstack或VisualVM
- 日志追踪:添加线程ID到日志
java复制// 线程感知日志示例
public class ThreadAwareLogger {
public static void debug(String message) {
System.out.printf("[%s] %s: %s%n",
LocalDateTime.now(),
Thread.currentThread().getName(),
message);
}
}
10.2 并发测试工具
- JUnit5并行测试:@Execution(ExecutionMode.CONCURRENT)
- TestNG多线程测试:@Test(threadPoolSize = 3)
- JCStress:Java并发压力测试工具
java复制// JCStress测试示例
@JCStressTest
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class ConcurrencyTest {
private int x, y;
@Actor
public void actor1() {
x = 1;
y = 1;
}
@Actor
public void actor2() {
while (y != 1) {} // 等待
x = 2;
}
@Arbiter
public void arbiter(IntResult2 r) {
r.r1 = x;
r.r2 = y;
}
}
10.3 常见并发Bug模式
- 竞态条件:检查时序依赖的操作
- 死锁:检查锁获取顺序
- 活锁:检查过度重试逻辑
- 资源泄漏:检查未释放的资源
- 上下文切换问题:检查volatile使用
java复制// 典型死锁示例
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void methodA() {
synchronized(lock1) {
synchronized(lock2) {
// 操作...
}
}
}
public void methodB() {
synchronized(lock2) {
synchronized(lock1) {
// 操作...
}
}
}
}
在实际开发中,理解这些并发概念和工具的使用场景至关重要。选择适当的并发控制机制需要权衡性能、复杂性和业务需求。建议从简单方案开始,如不可变对象或线程安全集合,仅在必要时使用更复杂的同步机制。