引言
在当今的多核处理器时代,并发编程已经成为Java开发者必须掌握的核心技能。合理运用并发技术不仅能提升应用性能,还能充分利用硬件资源。本文将深入探讨Java并发编程中的三大关键主题:线程池管理、锁机制选择以及性能优化策略。
一、线程池:高效的线程管理艺术
1.1 为什么需要线程池?
频繁创建和销毁线程会带来显著的性能开销。线程池通过复用线程资源,避免了这种开销,同时还提供了任务队列、线程数量控制等强大功能。
1.2 ThreadPoolExecutor核心参数详解
Java提供的ThreadPoolExecutor是线程池的核心实现,理解其参数至关重要:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
参数使用建议:
- corePoolSize: CPU密集型任务建议设置为CPU核心数+1,IO密集型任务可设置为2倍CPU核心数
- maximumPoolSize: 根据系统负载和内存情况合理设置
- workQueue: 常用的有ArrayBlockingQueue(有界队列)和LinkedBlockingQueue(可选有界/无界)
1.3 四种常见拒绝策略
当任务队列满且线程数达到最大值时,需要拒绝策略来处理新任务:
- AbortPolicy(默认): 直接抛出RejectedExecutionException
- CallerRunsPolicy: 由调用线程执行任务
- DiscardPolicy: 静默丢弃任务
- DiscardOldestPolicy: 丢弃队列中最旧的任务
1.4 实战示例:构建自定义线程池
public class CustomThreadPool {
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2;
private static final long KEEP_ALIVE_TIME = 60L;
public static ExecutorService createOptimizedPool() {
return new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadFactoryBuilder()
.setNameFormat("custom-pool-%d")
.setDaemon(false)
.build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
二、锁机制:并发安全的保障
2.1 synchronized:简单但强大
synchronized是Java内置的同步机制,使用简单但功能强大:
// 同步方法
public synchronized void synchronizedMethod() {
// 线程安全的代码
}
// 同步代码块
public void method() {
synchronized(this) {
// 临界区代码
}
}
优点: 使用简单,JVM自动管理锁的获取和释放 缺点: 不够灵活,无法实现公平锁、超时等待等高级特性
2.2 ReentrantLock:更灵活的选择
ReentrantLock提供了比synchronized更丰富的功能:
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void performTask() {
lock.lock();
try {
// 临界区代码
System.out.println("执行任务中...");
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
public void tryLockExample() {
if (lock.tryLock()) {
try {
// 获取锁成功
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他逻辑
}
}
}
2.3 ReadWriteLock:读写分离优化
对于读多写少的场景,ReadWriteLock能显著提升性能:
public class CacheWithReadWriteLock {
private final Map<String, Object> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public Object get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
public void put(String key, Object value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
}
2.4 锁选择的最佳实践
- synchronized: 简单场景、锁竞争不激烈时的首选
- ReentrantLock: 需要公平锁、超时、可中断等高级特性时使用
- ReadWriteLock: 读操作远多于写操作的场景
- StampedLock: JDK 8引入,提供乐观读锁,性能更优
三、性能优化:从理论到实践
3.1 减少锁的持有时间
锁的持有时间越短,并发性能越好:
// 不好的做法
public synchronized void badPractice() {
// 大量非临界区代码
heavyComputation();
// 临界区代码
sharedResource.update();
// 更多非临界区代码
moreComputation();
}
// 好的做法
public void goodPractice() {
heavyComputation();
synchronized(this) {
sharedResource.update(); // 只锁住必要的代码
}
moreComputation();
}
3.2 锁分段技术
ConcurrentHashMap使用的锁分段技术是减小锁粒度的经典案例:
public class SegmentedCache<K, V> {
private static final int SEGMENT_COUNT = 16;
private final Segment<K, V>[] segments;
static class Segment<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock();
public V get(K key) {
lock.lock();
try {
return map.get(key);
} finally {
lock.unlock();
}
}
}
private int hash(K key) {
return Math.abs(key.hashCode()) % SEGMENT_COUNT;
}
public V get(K key) {
return segments[hash(key)].get(key);
}
}
3.3 使用并发集合类
Java提供了丰富的线程安全集合类,性能远超同步包装类:
// 避免使用
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// 推荐使用
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
3.4 无锁编程:CAS操作
对于简单的原子操作,使用Atomic类可以避免锁开销:
public class LockFreeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作,无需加锁
}
public int get() {
return count.get();
}
// 复杂的原子操作
public void conditionalUpdate(int expected, int newValue) {
count.compareAndSet(expected, newValue);
}
}
3.5 避免伪共享
CPU缓存行的伪共享会严重影响多核并发性能:
// JDK 8之前
public class PaddedAtomicLong {
public volatile long p1, p2, p3, p4, p5, p6, p7; // 缓存行填充
public volatile long value;
public volatile long p9, p10, p11, p12, p13, p14, p15;
}
// JDK 8及以后
@sun.misc.Contended
public class ContendedCounter {
@sun.misc.Contended
private volatile long value;
}
四、实战案例:高并发订单处理系统
结合以上知识点,我们来设计一个高性能的订单处理系统:
public class OrderProcessingSystem {
private final ExecutorService executorService;
private final ConcurrentHashMap<String, Order> orderCache;
private final AtomicLong orderIdGenerator;
public OrderProcessingSystem() {
int processors = Runtime.getRuntime().availableProcessors();
this.executorService = new ThreadPoolExecutor(
processors,
processors * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
this.orderCache = new ConcurrentHashMap<>(1000);
this.orderIdGenerator = new AtomicLong(0);
}
public CompletableFuture<Order> submitOrder(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> {
// 生成订单ID
long orderId = orderIdGenerator.incrementAndGet();
// 创建订单
Order order = new Order(orderId, request);
// 处理订单逻辑
processOrder(order);
// 缓存订单
orderCache.put(String.valueOf(orderId), order);
return order;
}, executorService);
}
private void processOrder(Order order) {
// 订单处理逻辑
// 库存检查、支付处理等
}
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
}
}
五、性能监控与调优
5.1 监控线程池状态
public void monitorThreadPool(ThreadPoolExecutor executor) {
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("当前线程数: " + executor.getPoolSize());
System.out.println("活动线程数: " + executor.getActiveCount());
System.out.println("队列任务数: " + executor.getQueue().size());
System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
}
5.2 JVM参数调优
针对并发应用,关键的JVM参数包括:
# 设置堆内存
-Xms4g -Xmx4g
# 使用G1垃圾收集器(推荐)
-XX:+UseG1GC
# 设置GC线程数
-XX:ParallelGCThreads=8
# 启用GC日志
-Xlog:gc*:file=gc.log
总结
Java并发编程是一门需要深入理解和不断实践的技术。本文介绍的线程池管理、锁机制选择和性能优化策略,是构建高性能并发应用的基础。在实际开发中,我们需要根据具体场景选择合适的并发工具和策略,同时注重监控和调优,才能真正发挥并发编程的威力。
记住以下关键原则:
- 优先使用高级并发工具而非底层同步原语
- 减少锁的范围和持有时间
- 选择合适的线程池参数
- 使用并发集合类代替同步包装类
- 在合适的场景使用无锁编程
- 持续监控和优化性能
并发编程是一门艺术,需要在正确性和性能之间找到平衡。希望本文能为你的Java并发编程之旅提供有价值的参考。
1368

被折叠的 条评论
为什么被折叠?



