1. 线程安全的核心概念与实现机制
线程安全是多线程编程中最基础也最容易被误解的概念。简单来说,线程安全指的是当多个线程同时访问某个共享资源时,无论这些线程以何种顺序执行,程序都能产生正确的结果。听起来简单,但在实际开发中,线程安全问题往往是最难排查的bug来源之一。
1.1 为什么需要线程安全
想象一个银行账户系统,两个线程同时对一个账户进行存取款操作。如果没有适当的同步机制,可能会出现这样的情况:
- 线程A读取余额为100元
- 线程B也读取余额为100元
- 线程A存入50元,将余额更新为150元
- 线程B取出30元,将余额更新为70元
最终余额应该是120元(100+50-30),但由于线程不同步,导致实际结果为70元。这就是典型的线程安全问题。
1.2 实现线程安全的三种主要方式
1.2.1 不可变对象
最简单的线程安全实现方式是使用不可变对象。如果一个对象在创建后其状态不能被修改,那么它天生就是线程安全的。Java中的String类就是典型的不可变对象。
java复制public final class ImmutableValue {
private final int value;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
1.2.2 线程封闭
另一种思路是不共享数据,每个线程只访问自己的数据副本。ThreadLocal类就是这种思想的实现:
java复制public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocalValue.get();
value += 1;
threadLocalValue.set(value);
System.out.println(Thread.currentThread().getName() + ": " + value);
};
new Thread(task).start();
new Thread(task).start();
}
}
1.2.3 同步机制
当必须共享可变状态时,就需要使用同步机制。Java提供了多种同步工具:
- synchronized关键字
- volatile变量
- 原子类(AtomicInteger等)
- 显式锁(Lock接口)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程同步的三种加锁方式详解
2.1 synchronized关键字
synchronized是Java中最基本的同步机制,它可以用于方法或代码块:
java复制public class SynchronizedExample {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void incrementWithBlock() {
synchronized(this) {
count++;
}
}
}
注意:synchronized是重量级锁,在JDK1.6之后进行了优化,引入了偏向锁、轻量级锁等机制,但在高竞争环境下性能仍然较差。
2.2 ReentrantLock显式锁
ReentrantLock提供了比synchronized更灵活的锁机制:
java复制public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
ReentrantLock的优势:
- 可中断的锁获取
- 超时获取锁
- 公平锁与非公平锁选择
- 可以绑定多个条件(Condition)
2.3 读写锁(ReadWriteLock)
对于读多写少的场景,读写锁能显著提高性能:
java复制public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private Map<String, String> data = new HashMap<>();
public String get(String key) {
readLock.lock();
try {
return data.get(key);
} finally {
readLock.unlock();
}
}
public void put(String key, String value) {
writeLock.lock();
try {
data.put(key, value);
} finally {
writeLock.unlock();
}
}
}
3. 线程池的两种创建方式与核心参数
3.1 Executors工厂方法
Java提供了Executors工具类来创建常见的线程池:
java复制// 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(10);
// 单线程池
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 可缓存线程池
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
警告:在阿里巴巴Java开发规范中,明确禁止使用Executors创建线程池,而应该直接使用ThreadPoolExecutor构造函数。这是因为Executors提供的预设配置可能隐藏OOM风险。
3.2 ThreadPoolExecutor自定义创建
更推荐的方式是直接使用ThreadPoolExecutor构造函数:
java复制public class CustomThreadPool {
public static void main(String[] args) {
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
}
}
线程池的七个核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程空闲存活时间
- unit:时间单位
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
3.3 队列容量与并发量的关系
队列容量(queueCapacity)的设置需要根据系统负载和业务特点来决定:
- 队列太小:容易触发拒绝策略,导致任务丢失
- 队列太大:可能耗尽内存,且任务响应延迟高
经验公式:
code复制最大并发量 ≈ 最大线程数 + 队列容量
对于CPU密集型任务,建议:
- 核心线程数 = CPU核心数
- 最大线程数 = CPU核心数 * 1.5
- 队列容量 = 100~1000
对于IO密集型任务,建议:
- 核心线程数 = CPU核心数 * 2
- 最大线程数 = CPU核心数 * 4
- 队列容量 = 1000~10000
4. 线程池处理Runnable与Callable任务
4.1 执行Runnable任务
Runnable是没有返回值的任务:
java复制public class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("Executing Runnable task in " + Thread.currentThread().getName());
}
}
// 提交任务
executor.execute(new RunnableTask());
4.2 执行Callable任务
Callable是可以返回结果和抛出异常的任务:
java复制public class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Result from " + Thread.currentThread().getName();
}
}
// 提交任务并获取Future
Future<String> future = executor.submit(new CallableTask());
String result = future.get(); // 阻塞获取结果
4.3 Future与CompletableFuture
Java 8引入了更强大的CompletableFuture:
java复制CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Result";
}, executor).thenAccept(result -> {
System.out.println("Got result: " + result);
}).exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return null;
});
5. 并发与并行的本质区别
5.1 并发(Concurrency)
并发是指多个任务在重叠的时间段内执行,但不一定是同时执行。在单核CPU上,通过时间片轮转实现并发。
5.2 并行(Parallelism)
并行是指多个任务真正同时执行,需要多核CPU支持。
5.3 实际应用中的考量
-
并发编程关注点:
- 任务分解
- 资源共享与同步
- 死锁避免
- 线程间通信
-
并行编程关注点:
- 数据分区
- 负载均衡
- 减少通信开销
- 扩展性
6. 线程池实战经验与避坑指南
6.1 线程池配置的黄金法则
- 不要使用无界队列:可能导致OOM
- 合理设置线程存活时间:避免资源浪费
- 为线程池设置有意义的名称:便于问题排查
- 监控线程池状态:活跃线程数、队列大小等
- 选择合适的拒绝策略:
- AbortPolicy:直接抛出异常(默认)
- CallerRunsPolicy:由调用线程执行任务
- DiscardPolicy:静默丢弃任务
- DiscardOldestPolicy:丢弃队列中最老的任务
6.2 常见问题排查
问题1:线程池中的任务卡住
排查步骤:
- 使用jstack获取线程转储
- 查找线程池中的工作线程状态
- 检查是否有死锁或资源等待
问题2:任务执行速度慢
可能原因:
- 线程数设置不足
- 任务之间存在资源竞争
- 单个任务执行时间过长
解决方案:
- 增加线程数(不超过maximumPoolSize)
- 优化任务代码
- 考虑任务拆分
6.3 性能调优技巧
- 使用ThreadPoolExecutor的扩展点:
java复制public class MonitorThreadPool extends ThreadPoolExecutor {
@Override
protected void beforeExecute(Thread t, Runnable r) {
// 记录任务开始时间
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
// 记录任务结束时间和异常
}
}
- 动态调整线程池参数:
java复制// 动态修改核心线程数
executor.setCorePoolSize(newCorePoolSize);
// 动态修改最大线程数
executor.setMaximumPoolSize(newMaximumPoolSize);
- 使用ForkJoinPool处理分治任务:
java复制public class RecursiveTaskExample extends RecursiveTask<Long> {
@Override
protected Long compute() {
// 实现分治逻辑
}
}
ForkJoinPool pool = new ForkJoinPool();
RecursiveTaskExample task = new RecursiveTaskExample();
Long result = pool.invoke(task);
7. Java并发工具包进阶
7.1 CountDownLatch应用场景
CountDownLatch适用于需要等待多个线程完成后再继续执行的场景:
java复制public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep((long)(Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("All threads finished");
}
}
7.2 CyclicBarrier同步屏障
CyclicBarrier适用于多线程相互等待的场景:
java复制public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("All threads reached the barrier");
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " is waiting");
barrier.await();
System.out.println(Thread.currentThread().getName() + " continues");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
7.3 Semaphore信号量控制
Semaphore用于控制同时访问特定资源的线程数量:
java复制public class SemaphoreExample {
private static final int PERMITS = 3;
private static Semaphore semaphore = new Semaphore(PERMITS);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " acquired permit");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + " released permit");
}
}).start();
}
}
}
8. 现代并发编程趋势
8.1 响应式编程与Project Loom
Java正在向更轻量级的并发模型发展。Project Loom引入了虚拟线程(Virtual Threads),可以创建数百万个轻量级线程:
java复制// JDK 19+ 预览特性
Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread");
});
8.2 结构化并发
结构化并发(Structured Concurrency)是一种更安全的并发编程范式,确保子任务的生命周期不会超过父任务:
java复制try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // 等待两个子任务
scope.throwIfFailed(); // 如果有异常则抛出
System.out.println("User: " + user.resultNow() + ", Order: " + order.resultNow());
}
8.3 并发集合的选择
根据不同的并发需求选择合适的集合:
| 需求 | 推荐实现类 |
|---|---|
| 高并发读 | CopyOnWriteArrayList |
| 高并发写 | ConcurrentLinkedQueue |
| 键值对存储 | ConcurrentHashMap |
| 排序需求 | ConcurrentSkipListMap |
| 阻塞队列 | LinkedBlockingQueue |
| 延迟队列 | DelayQueue |
| 优先级队列 | PriorityBlockingQueue |
在实际项目中,线程安全和并发控制是保证系统稳定性的关键。从基础的synchronized到复杂的线程池调优,再到现代的并发工具和编程模型,Java提供了丰富的并发编程工具集。掌握这些工具的正确使用方式,能够帮助开发者构建高性能、高可靠的并发应用程序。
