1. 线程池与并发安全的关系
线程池的核心作用是管理和复用线程,它本身是线程安全的(如ThreadPoolExecutor的内部实现通过锁机制保证线程安全)。但线程池执行的任务逻辑如果涉及共享资源的访问,就可能引发并发问题。
关键点:
- 线程池只是提供执行环境,不负责任务内部的并发安全。
- 多个线程同时访问共享资源(如静态变量、成员变量)时,需手动保证同步。
2. 可能出现的并发安全问题
示例 1:多线程修改共享变量
java
public class ThreadPoolSafetyDemo {
private static int counter = 0; // 共享变量
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交1000个任务
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
counter++; // 非原子操作,线程不安全
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("Expected: 1000, Actual: " + counter); // 可能小于1000
}
}
问题:counter++ 不是原子操作,存在竞态条件(Race Condition)。
示例 2:线程不安全的对象
java
public class ThreadPoolSafetyDemo {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 非线程安全
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final int date = i;
executor.submit(() -> {
try {
sdf.parse("2023-" + date % 12 + "-" + date % 30); // 可能抛出异常
} catch (Exception e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
问题:SimpleDateFormat 不是线程安全的,多线程共用会导致解析错误。
3. 如何保证线程池中的并发安全?
方法 1:使用线程安全的数据结构
java
private static AtomicInteger counter = new AtomicInteger(0); // 原子类
executor.submit(() -> {
counter.incrementAndGet(); // 原子操作,线程安全
});
方法 2:同步机制(锁)
java
private static final Object lock = new Object();
private static int counter = 0;
executor.submit(() -> {
synchronized (lock) {
counter++; // 同步块,线程安全
}
});
方法 3:避免共享状态
java
executor.submit(() -> {
SimpleDateFormat localSdf = new SimpleDateFormat("yyyy-MM-dd"); // 局部变量
localSdf.parse("2023-01-01"); // 无共享,线程安全
});
方法 4:使用线程安全的工具类
java
// 使用ThreadLocal为每个线程创建独立的SimpleDateFormat实例
private static final ThreadLocal<SimpleDateFormat> threadLocalSdf =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
executor.submit(() -> {
SimpleDateFormat sdf = threadLocalSdf.get(); // 线程独享
sdf.parse("2023-01-01");
});
4. 线程池配置对并发的影响
线程池参数设置不当可能间接导致问题:
- 核心线程数 / 最大线程数过大:导致过多线程同时执行,增加竞争。
- 任务队列过长:可能积压大量任务,导致内存溢出。
优化建议:
java
// 根据业务场景调整线程池参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(100), // 任务队列大小
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
5. 面试应答要点
问题:线程池中会不会出现并发安全问题?
参考答案:
- 线程池本身是线程安全的,但其执行的任务如果涉及共享资源(如静态变量、成员变量),可能出现并发问题。
- 常见问题:竞态条件、数据不一致、对象状态异常等。
- 解决方法:
- 使用线程安全的数据结构(如
AtomicInteger、ConcurrentHashMap)。 - 加锁(
synchronized、ReentrantLock)。 - 避免共享状态(使用局部变量或
ThreadLocal)。
- 使用线程安全的数据结构(如
- 线程池配置优化:根据业务调整线程数和队列大小,避免资源耗尽。
示例:在某项目中,我们曾因多线程共用SimpleDateFormat导致解析错误,通过ThreadLocal修复了该问题。