线程池与多线程安全

线程池是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监控线程状态

扩展阅读方向

  1. Java内存模型(JMM)与happens-before规则
  2. 无锁算法与CAS实现原理
  3. Fork/Join框架处理分治任务
  4. CompletableFuture异步编程
  5. 响应式编程中的线程安全考虑

响应式编程中的线程安全考虑

线程安全的基本概念

在响应式编程中,线程安全是指当多个线程同时访问某个数据结构或执行某个操作时,系统仍能保持正确性。响应式框架通常采用异步非阻塞的设计模式,这使得线程安全问题尤为突出。常见的线程安全问题包括:

  • 竞态条件(Race Condition)
  • 死锁(Deadlock)
  • 内存可见性问题(Memory Visibility)