线程池与多线程安全
线程池是Java多线程编程中的核心工具,通过复用线程减少创建销毁开销。java.util.concurrent包提供ThreadPoolExecutor作为线程池实现基础,其工作流程包含核心线程、任务队列和最大线程数三个关键参数。
线程安全问题本质是共享数据的竞态条件。当多个线程同时访问同一资源且至少有一个线程执行写操作时,如果没有正确同步,可能导致数据不一致。典型场景包括银行转账、库存扣减等。
线程安全问题的常见类型
竞态条件(Race Condition)
多个线程交替执行时序导致意外结果。例如i++操作实际包含读取-修改-写入三步,非原子性操作。
内存可见性问题
由于CPU缓存和指令重排序,线程可能读取到过期的共享变量值。volatile关键字可解决部分可见性问题。
死锁/活锁 多个线程互相持有对方所需资源导致永久阻塞,或重复尝试失败的操作消耗CPU资源。
线程安全防护的核心手段
同步代码块
使用synchronized关键字保护临界区:
synchronized(lockObject) {
// 访问共享资源
}
显式锁机制
ReentrantLock提供更灵活的锁操作:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
原子变量类
AtomicInteger等原子类通过CAS实现无锁线程安全:
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
线程封闭技术
将对象限制在单线程内访问,如ThreadLocal变量:
ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
线程池配置的最佳实践
核心参数设置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100) // 任务队列
);
拒绝策略选择
- AbortPolicy(默认):抛出RejectedExecutionException
- CallerRunsPolicy:由提交任务的线程执行
- DiscardPolicy:静默丢弃任务
- DiscardOldestPolicy:丢弃队列最老任务
线程工厂定制 为线程设置可识别名称和异常处理器:
ThreadFactory factory = r -> {
Thread t = new Thread(r, "worker-" + counter.getAndIncrement());
t.setUncaughtExceptionHandler((thread, ex) ->
System.err.println("Exception in " + thread.getName() + ": " + ex));
return t;
};
典型线程安全案例解析
案例1:计数器同步 错误实现:
class UnsafeCounter {
private int count;
public void increment() { count++; }
}
修正方案1(同步方法):
class SafeCounter {
private int count;
public synchronized void increment() { count++; }
}
修正方案2(原子变量):
class SafeCounter {
private AtomicInteger count = new AtomicInteger();
public void increment() { count.incrementAndGet(); }
}
案例2:缓存系统实现
使用ConcurrentHashMap实现线程安全缓存:
class CacheSystem {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<>();
public Object get(String key) {
return cache.computeIfAbsent(key, k -> {
// 加载数据的耗时操作
return loadFromDatabase(k);
});
}
}
高级并发工具应用
CountDownLatch 多线程任务协同:
CountDownLatch latch = new CountDownLatch(3);
executor.execute(() -> {
doWork();
latch.countDown();
});
latch.await(); // 阻塞直到计数器归零
CyclicBarrier 线程集合点同步:
CyclicBarrier barrier = new CyclicBarrier(5, () ->
System.out.println("所有线程到达屏障点"));
executor.execute(() -> {
prepareData();
barrier.await();
});
Semaphore 资源访问控制:
Semaphore semaphore = new Semaphore(3); // 允许3个并发访问
executor.execute(() -> {
semaphore.acquire();
try {
useLimitedResource();
} finally {
semaphore.release();
}
});
性能优化与权衡
锁粒度控制
- 细粒度锁:减小竞争范围但增加管理复杂度
- 粗粒度锁:实现简单但可能降低并发度
减少锁持有时间 将非临界区代码移出同步块:
synchronized(this) {
// 只包含必须同步的操作
}
// 其他非同步操作
读写锁应用
ReentrantReadWriteLock适合读多写少场景:
ReadWriteLock rwLock = new ReentrantReadWriteLock();
void readData() {
rwLock.readLock().lock();
try { /* 读取操作 */ }
finally { rwLock.readLock().unlock(); }
}
void writeData() {
rwLock.writeLock().lock();
try { /* 写入操作 */ }
finally { rwLock.writeLock().unlock(); }
}
线程池监控与调优
监控关键指标
ThreadPoolExecutor executor = ...;
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("完成任务数: " + executor.getCompletedTaskCount());
System.out.println("队列大小: " + executor.getQueue().size());
动态调整参数
executor.setCorePoolSize(newCoreSize);
executor.setMaximumPoolSize(newMaxSize);
异常处理策略
实现Thread.UncaughtExceptionHandler全局捕获未处理异常:
executor.setThreadFactory(r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, ex) ->
logger.error("Thread {} failed: {}", thread.getName(), ex));
return t;
});
并发设计模式实践
Worker Thread模式 任务与执行线程解耦:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> processTask(task));
Producer-Consumer模式 通过阻塞队列实现:
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
// 生产者
executor.submit(() -> queue.put(createTask()));
// 消费者
executor.submit(() -> processTask(queue.take()));
Thread-Per-Message模式 为每个请求创建独立线程:
void handleRequest(Request request) {
new Thread(() -> processRequest(request)).start();
}
避免常见陷阱
死锁预防
- 按固定顺序获取多个锁
- 使用
tryLock设置超时 - 静态代码分析工具检测潜在死锁
资源泄漏防范 确保线程池正确关闭:
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
上下文切换开销
- 避免过度创建线程
- 使用有界队列防止OOM
- 考虑协程(如Quasar/Kotlin协程)减少切换成本
综合实战案例
电商秒杀系统实现
class SeckillService {
private final AtomicInteger stock = new AtomicInteger(100);
private final ConcurrentMap<Long, Boolean> dedup = new ConcurrentHashMap<>();
public Result seckill(User user, Long itemId) {
// 幂等检查
if (dedup.putIfAbsent(user.getId()+itemId, true) != null) {
return Result.fail("重复请求");
}
// 库存检查
int remain = stock.get();
if (remain <= 0) return Result.fail("已售罄");
// CAS减库存
if (!stock.compareAndSet(remain, remain-1)) {
return Result.fail("竞争失败");
}
// 创建订单(异步化)
orderExecutor.submit(() -> createOrder(user, itemId));
return Result.success();
}
}
测试与验证方法
并发测试工具 使用JMeter或代码模拟并发:
IntStream.range(0, 1000).parallel().forEach(i -> {
service.increment();
});
assert service.getCount() == 1000;
静态分析工具
- FindBugs/SpotBugs检测同步问题
- JProfiler分析锁竞争情况
- Java Mission Control监控线程状态
扩展阅读方向
- Java内存模型(JMM)与happens-before规则
- 无锁算法与CAS实现原理
- Fork/Join框架处理分治任务
- CompletableFuture异步编程
- 响应式编程中的线程安全考虑
响应式编程中的线程安全考虑
线程安全的基本概念
在响应式编程中,线程安全是指当多个线程同时访问某个数据结构或执行某个操作时,系统仍能保持正确性。响应式框架通常采用异步非阻塞的设计模式,这使得线程安全问题尤为突出。常见的线程安全问题包括:
- 竞态条件(Race Condition)
- 死锁(Deadlock)
- 内存可见性问题(Memory Visibility)
















