目录
5.2 ReentrantLock 与 ReadWriteLock
一、并发编程的定义与重要性
1.1 并发与并行的本质区别
并发(Concurrency)指多个任务交替执行,适用于单核 CPU 环境,通过时间片轮转实现逻辑上的同时执行。例如,早期单核手机同时运行音乐播放和消息推送。
并行(Parallelism)指多个任务真正同时执行,依赖多核 CPU,每个任务分配独立核心。例如,现代服务器利用多核 CPU 同时处理多个 HTTP 请求。
1.2 Java 并发编程的核心价值
- 资源利用率最大化
通过线程复用和任务调度,充分利用多核 CPU、I/O 设备等资源。例如,数据库连接池通过并发管理提升查询效率。 - 响应速度优化
多线程处理异步任务,避免主线程阻塞。例如,电商平台的订单处理线程与库存更新线程并行执行。 - 系统吞吐量提升
高并发场景下,通过线程池和异步队列提升单位时间处理能力。例如,秒杀系统通过并发控制实现每秒万级请求处理。
1.3 企业级应用中的核心地位
- 分布式系统:微服务架构中,每个服务通过多线程处理客户端请求。
- 实时计算:Flink、Kafka 等流处理框架依赖并发机制实现实时数据处理。
- 高性能中间件:Netty、Tomcat 等网络框架通过 NIO 和多线程模型提升 I/O 效率。
二、Java 并发编程核心概念
2.1 线程与进程
- 进程:资源分配的基本单位,拥有独立内存空间。
- 线程:CPU 调度的基本单位,共享进程内存,包含程序计数器、栈等独立上下文。
2.2 同步与锁
- 同步机制:确保多线程访问共享资源的原子性和可见性。
- 锁类型:
- 互斥锁:synchronized、ReentrantLock,保证同一时刻只有一个线程访问资源。
- 读写锁:ReadWriteLock,允许多个读线程同时访问,写线程独占。
2.3 并发集合类
- ConcurrentHashMap:Java 8 后采用 CAS+Synchronized + 红黑树结构,替代分段锁,提升并发性能。
- CopyOnWriteArrayList:写时复制实现线程安全,适用于读多写少场景。
- BlockingQueue:支持阻塞操作的队列,如 ArrayBlockingQueue、LinkedBlockingQueue,常用于生产者 - 消费者模式。
三、Java 并发编程应用场景
3.1 高性能计算
- 矩阵运算:将矩阵分块,通过多线程并行计算提升效率。
- 数据挖掘:MapReduce 框架通过分布式并发处理海量数据。
3.2 分布式系统
- 服务发现:Eureka 通过多线程实现服务注册与心跳检测。
- 分布式锁:Redis 通过 SETNX 命令实现分布式环境下的资源互斥。
3.3 实时通信
- WebSocket 服务器:Netty 通过多线程模型实现高并发实时消息推送。
- IM 系统:通过长连接和多线程处理实现消息的即时传输。
四、线程管理与调度
4.1 线程创建与启动
4.1.1 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
new MyThread().start();
4.1.2 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
new Thread(new MyRunnable()).start();
4.1.3 实现 Callable 接口(支持返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
String result = futureTask.get();
4.2 线程生命周期
- 新建(New):线程对象创建但未启动。
- 就绪(Runnable):线程调用 start () 方法,等待 CPU 调度。
- 运行(Running):获得 CPU 时间片执行任务。
- 阻塞(Blocked):因等待锁、I/O 等资源暂停执行。
- 死亡(Terminated):线程正常结束或异常终止。
4.3 线程池深度解析
4.3.1 核心参数配置
- corePoolSize:核心线程数,通常设为 CPU 核心数(Runtime.getRuntime ().availableProcessors ())。
- maximumPoolSize:最大线程数,建议设为 corePoolSize 的 2 倍。
- keepAliveTime:非核心线程空闲存活时间,建议 60 秒。
- workQueue:任务队列,推荐使用有界队列(如 ArrayBlockingQueue)防止内存溢出。
- RejectedExecutionHandler:拒绝策略,推荐 CallerRunsPolicy(调用方线程执行任务)。
4.3.2 线程池类型
- FixedThreadPool:固定大小线程池,适用于稳定负载场景。
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
- CachedThreadPool:可缓存线程池,适用于短期异步任务。
ExecutorService cachedPool = Executors.newCachedThreadPool();
- SingleThreadExecutor:单线程线程池,保证任务顺序执行。
ExecutorService singlePool = Executors.newSingleThreadExecutor();
- ScheduledThreadPool:定时线程池,支持延迟或周期性任务。
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2); scheduledPool.scheduleAtFixedRate(() -> { System.out.println("Scheduled task"); }, 1, 5, TimeUnit.SECONDS);
4.3.3 性能优化策略
- CPU 密集型任务:线程数 = CPU 核心数,减少上下文切换。
- IO 密集型任务:线程数 = CPU 核心数 ×2,充分利用等待 I/O 时间。
- 动态调整线程池:根据实时负载调整 corePoolSize 和 maximumPoolSize。
4.4 线程调度与优先级
- 线程优先级:1(最低)~10(最高),默认 5。
- 调度策略:
- 抢占式调度:高优先级线程优先获得 CPU 时间片。
- 协同式调度:线程主动让出 CPU(如 yield () 方法)。
五、同步与锁机制
5.1 synchronized 关键字
5.1.1 使用方式
- 修饰实例方法:锁定当前对象。
public synchronized void syncMethod() { // 同步代码块 }
- 修饰静态方法:锁定当前类的 Class 对象。
public static synchronized void staticSyncMethod() { // 同步代码块 }
- 修饰代码块:锁定指定对象。
Object lock = new Object(); public void syncBlock() { synchronized (lock) { // 同步代码块 } }
5.1.2 底层实现
- Monitor 机制:通过对象头中的 Mark Word 标记锁状态,分为偏向锁、轻量级锁、重量级锁。
- 锁升级过程:无锁→偏向锁→轻量级锁→重量级锁(不可逆)。
5.1.3 局限性
- 功能单一:不支持公平锁、可中断锁、条件变量。
- 性能瓶颈:高竞争场景下锁升级带来的上下文切换开销较大。
5.2 ReentrantLock 与 ReadWriteLock
5.2.1 ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
特性:
- 可重入性:同一线程可多次获取锁,通过计数器实现。
- 公平锁与非公平锁:默认非公平锁(性能更优),可通过构造函数设置公平性。
- 可中断锁:支持 lockInterruptibly () 方法响应中断。
5.2.2 ReadWriteLock
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁
rwLock.readLock().lock();
try {
// 读操作
} finally {
rwLock.readLock().unlock();
}
// 写锁
rwLock.writeLock().lock();
try {
// 写操作
} finally {
rwLock.writeLock().unlock();
}
应用场景:
- 读多写少场景:如缓存系统,读操作共享锁,写操作独占锁。
5.3 锁的公平性与非公平性
- 公平锁:线程按申请顺序获取锁,避免线程饥饿,但性能较低。
- 非公平锁:允许线程抢占锁,性能更优,但可能导致部分线程长时间等待。
性能对比:
- 低竞争场景:synchronized 与 ReentrantLock 性能相近。
- 高竞争场景:ReentrantLock 通过可中断锁和条件变量表现更优。
六、死锁预防与检测
6.1 死锁的四个必要条件
- 互斥条件:资源只能被一个线程占用。
- 请求与保持条件:线程持有资源的同时请求其他资源。
- 不可剥夺条件:资源只能由持有线程主动释放。
- 循环等待条件:线程间形成资源请求环。
6.2 死锁预防策略
- 破坏请求与保持条件:一次性申请所有资源。
- 破坏不可剥夺条件:允许抢占资源(如超时释放锁)。
- 破坏循环等待条件:按资源序号申请资源。
6.3 死锁检测与解除
6.3.1 使用 jstack 工具
- 获取进程 ID:
jps
- 生成线程堆栈:
jstack <pid>
- 分析死锁:查找
Found one Java-level deadlock
标记。
6.3.2 使用 VisualVM
- 打开 VisualVM,选择目标进程。
- 切换到 “线程” 选项卡,查看死锁线程的堆栈信息。
6.3.3 代码检测
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
for (long threadId : deadlockedThreads) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
System.out.println("Deadlocked thread: " + threadInfo.getThreadName());
}
}
七、企业级实践与优化
7.1 并发集合类的选择
- 高并发读:ConcurrentHashMap(Java 8 后性能显著提升)。
- 读多写少:CopyOnWriteArrayList、ReadWriteLock。
- 生产者 - 消费者模式:BlockingQueue(如 ArrayBlockingQueue)。
7.2 性能优化技巧
- 减少锁粒度:如 ConcurrentHashMap 的分段锁优化。
- 无锁化设计:使用 Atomic 类、CAS 操作替代锁。
- 线程本地化存储:ThreadLocal 避免共享资源竞争。
7.3 分布式锁实现
- Redis 分布式锁:通过 SETNX 命令实现,需考虑锁超时和主从同步问题。
- ZooKeeper 分布式锁:利用临时顺序节点实现公平锁。
八、总结与最佳实践
- 优先使用线程池:避免频繁创建销毁线程,合理配置核心参数。
- 谨慎选择锁机制:简单场景用 synchronized,复杂场景用 ReentrantLock。
- 避免死锁:遵循资源申请顺序,设置锁超时时间。
- 监控与调优:通过 jstack、VisualVM 等工具实时监控线程状态。
Java 并发编程是构建高性能、高可用系统的核心技能。通过深入理解线程管理、同步机制、锁优化等核心知识,结合企业级实践,开发者能够高效应对高并发挑战,打造健壮的分布式系统。