1. Java多线程基础回顾与核心概念
在上一篇文章中,我们已经探讨了Java多线程的基础知识,包括线程的创建方式、生命周期管理等。现在让我们快速回顾几个关键概念:
- 线程创建:继承Thread类 vs 实现Runnable接口
- 线程状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
- 线程优先级:1-10的优先级设置(但实际调度依赖操作系统)
重要提示:虽然Thread类提供了setPriority()方法,但在实际开发中过度依赖线程优先级通常会导致不可预期的行为,因为最终调度权在操作系统手中。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程同步与锁机制深度解析
2.1 synchronized关键字实战
synchronized是Java中最基础的线程同步机制,它可以用于:
java复制// 同步方法
public synchronized void method() {
// 临界区代码
}
// 同步代码块
public void method() {
synchronized(this) {
// 临界区代码
}
}
在实际项目中,我强烈建议使用同步代码块而非同步方法,因为:
- 粒度更细,锁的范围更小
- 可以灵活选择锁对象
- 避免意外锁住整个方法导致性能下降
2.2 ReentrantLock的进阶使用
相比synchronized,ReentrantLock提供了更灵活的锁机制:
java复制Lock lock = new ReentrantLock();
try {
lock.lock();
// 临界区代码
} finally {
lock.unlock(); // 必须放在finally块中
}
ReentrantLock的高级特性包括:
- 可中断的锁获取
- 超时获取锁
- 公平锁与非公平锁
- 多个条件变量(Condition)
实战经验:在JDK1.6之后,synchronized性能已经大幅提升,仅在需要ReentrantLock特有功能时才使用它,不要为了"看起来高级"而过度使用。
3. Java内存模型(JMM)与volatile关键字
3.1 可见性问题解析
考虑以下代码:
java复制public class VisibilityProblem {
private boolean flag = true;
public void run() {
while(flag) {
// 空循环
}
System.out.println("Stopped");
}
public void stop() {
flag = false;
}
}
即使在一个线程中调用stop(),另一个线程的循环可能永远不会停止。这是因为:
- 每个线程有自己的工作内存
- 对普通变量的修改可能不会立即刷新到主内存
- 其他线程可能看不到变量的最新值
3.2 volatile的语义与限制
volatile变量解决了可见性问题:
java复制private volatile boolean flag = true;
volatile保证了:
- 可见性:写操作立即刷新到主内存
- 禁止指令重排序
但它不保证原子性!例如count++这样的操作即使使用volatile也不是线程安全的。
4. 线程池的深度实践与调优
4.1 Executor框架详解
Java提供了强大的线程池实现:
java复制ExecutorService executor = Executors.newFixedThreadPool(4);
但实际项目中,我建议直接使用ThreadPoolExecutor构造函数:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100) // 工作队列
);
4.2 线程池参数调优经验
- 核心线程数:通常设置为CPU核心数或略多
- 最大线程数:根据任务特性设置,IO密集型可以设置较大值
- 队列选择:
- SynchronousQueue:直接传递,无缓冲
- ArrayBlockingQueue:有界队列
- LinkedBlockingQueue:无界队列(慎用)
- 拒绝策略:
- AbortPolicy:默认,抛出异常
- CallerRunsPolicy:由调用线程执行
- DiscardPolicy:静默丢弃
- DiscardOldestPolicy:丢弃最老任务
避坑指南:使用无界队列时,如果任务生产速度持续高于消费速度,最终会导致OOM。我曾在一个项目中因为这个原因导致服务崩溃,后来改用有界队列+合适的拒绝策略解决了问题。
5. 并发工具类实战应用
5.1 CountDownLatch使用场景
CountDownLatch非常适合主线程等待多个子线程完成的场景:
java复制CountDownLatch latch = new CountDownLatch(3);
// 工作线程
new Thread(() -> {
try {
// 执行任务
} finally {
latch.countDown();
}
}).start();
// 主线程等待
latch.await();
5.2 CyclicBarrier与阶段同步
CyclicBarrier适用于多个线程互相等待的场景:
java复制CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 所有线程到达屏障后执行
});
new Thread(() -> {
// 第一阶段工作
barrier.await();
// 第二阶段工作
}).start();
5.3 Semaphore实现资源控制
Semaphore可以控制同时访问特定资源的线程数量:
java复制Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问
try {
semaphore.acquire();
// 使用资源
} finally {
semaphore.release();
}
6. 原子类与CAS原理
6.1 AtomicInteger等原子类
Java提供了一系列原子类来解决复合操作的原子性问题:
java复制AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // 原子自增
6.2 CAS原理与ABA问题
CAS(Compare-And-Swap)是原子类的底层实现原理:
java复制public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
ABA问题是指:
- 线程1读取值为A
- 线程2将值改为B又改回A
- 线程1的CAS操作仍然成功
解决方案是使用AtomicStampedReference或AtomicMarkableReference。
7. 并发集合类性能对比
7.1 ConcurrentHashMap深度解析
ConcurrentHashMap是线程安全的HashMap实现,在JDK8中进行了重大改进:
- 取消了分段锁,改用CAS+synchronized
- 当链表长度超过8时转为红黑树
- 扩容时支持多线程协助
使用示例:
java复制ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> 1);
7.2 CopyOnWriteArrayList适用场景
适合读多写少的场景,写操作时复制整个数组:
java复制List<String> list = new CopyOnWriteArrayList<>();
list.add("item"); // 写时复制
性能提示:当写操作频繁时,CopyOnWriteArrayList性能会急剧下降,因为它每次写操作都需要复制整个底层数组。
8. 线程安全设计模式与最佳实践
8.1 不可变对象模式
最简单的线程安全方案是使用不可变对象:
java复制public final class ImmutableValue {
private final int value;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
8.2 线程封闭技术
通过限制对象只能由单个线程访问来避免并发问题:
- 栈封闭:局部变量本身就是线程封闭的
- ThreadLocal:为每个线程保存独立的副本
ThreadLocal使用示例:
java复制private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
8.3 常见并发陷阱与解决方案
- 死锁:使用jstack检测,遵循固定的锁获取顺序
- 活锁:引入随机退避机制
- 线程饥饿:使用公平锁或调整线程优先级
我在实际项目中遇到过这样一个死锁案例:
- 线程A持有锁1,尝试获取锁2
- 线程B持有锁2,尝试获取锁1
解决方案是统一锁的获取顺序,或者使用tryLock()带超时机制。
9. Java虚拟线程(Loom项目)前瞻
虽然还未正式发布,但Java虚拟线程(纤程)值得关注:
- 轻量级线程,由JVM管理调度
- 可以创建数百万个而不会耗尽资源
- 与现有代码兼容
示例代码(预览API):
java复制Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread");
});
未来展望:虚拟线程可能会改变Java高并发编程的范式,特别是在IO密集型应用中。但目前生产环境还不建议使用,可以保持关注。
