1. 多线程基础概念与核心机制
在Java并发编程中,理解线程的基本概念和工作原理是至关重要的基础。让我们从一个实际场景开始:假设你正在开发一个电商系统,需要同时处理用户下单、库存扣减和物流调度等多个任务。如果使用单线程处理,用户可能需要等待很长时间才能完成整个购买流程。这时,多线程就能派上用场了。
1.1 线程的生命周期与状态转换
Java线程在其生命周期中会经历多种状态变化,这些状态定义在Thread.State枚举中:
- NEW(新建):线程被创建但尚未启动
- RUNNABLE(可运行):线程正在JVM中执行或等待操作系统资源
- BLOCKED(阻塞):线程被阻塞等待监视器锁
- WAITING(等待):线程无限期等待其他线程执行特定操作
- TIMED_WAITING(定时等待):线程在指定时间内等待
- TERMINATED(终止):线程已完成执行
在实际开发中,我曾遇到一个典型问题:系统监控显示大量线程处于BLOCKED状态,导致系统吞吐量下降。通过分析发现,这是因为多个线程竞争同一个数据库连接池锁造成的。解决方案是增加连接池大小和优化锁粒度。
1.2 线程创建与启动机制
创建线程有两种基本方式:继承Thread类和实现Runnable接口。但在实际项目中,我们更推荐使用线程池来管理线程资源。来看一个线程创建的典型示例:
java复制// 方式1:继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running by extending Thread");
}
}
// 方式2:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread running by implementing Runnable");
}
}
public class ThreadCreation {
public static void main(String[] args) {
// 方式1使用
MyThread thread1 = new MyThread();
thread1.start();
// 方式2使用
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// Java8 Lambda简化写法
Thread thread3 = new Thread(() ->
System.out.println("Thread running via Lambda"));
thread3.start();
}
}
重要提示:直接调用run()方法不会启动新线程,它只会在当前线程中同步执行方法体。只有调用start()方法才会真正创建新线程并异步执行run()方法。
2. 线程同步与通信机制
2.1 synchronized关键字与对象监视器
synchronized是Java中最基本的同步机制,它基于对象的内置锁(监视器锁)实现同步。在电商系统中,库存扣减就需要这样的同步控制:
java复制public class Inventory {
private int stock = 100;
public synchronized void decreaseStock(int quantity) {
if (stock >= quantity) {
stock -= quantity;
System.out.println("扣减成功,剩余库存:" + stock);
} else {
System.out.println("库存不足");
}
}
}
synchronized可以修饰实例方法(锁当前实例)、静态方法(锁Class对象)和代码块(指定锁对象)。我曾经在一个高并发场景下,错误地使用了不同锁对象导致数据不一致,最终通过统一锁对象解决了问题。
2.2 wait/notify机制详解
wait()和notify()是Object类提供的线程间通信机制,必须配合synchronized使用。典型的生产者-消费者模式实现:
java复制public class MessageQueue {
private final Queue<String> queue = new LinkedList<>();
private final int maxSize;
public MessageQueue(int maxSize) {
this.maxSize = maxSize;
}
public synchronized void put(String message) throws InterruptedException {
while (queue.size() == maxSize) {
wait(); // 队列满时等待
}
queue.add(message);
notifyAll(); // 通知消费者
}
public synchronized String take() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 队列空时等待
}
String message = queue.remove();
notifyAll(); // 通知生产者
return message;
}
}
关键经验:必须使用while循环而不是if判断来检查条件,防止虚假唤醒。这是Java官方明确建议的做法。
2.3 Lock接口与Condition
Java 5引入了更灵活的Lock接口及其关联的Condition,提供了比synchronized更丰富的功能:
java复制public class ReentrantLockDemo {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Queue<String> queue = new LinkedList<>();
private final int maxSize = 10;
public void put(String message) throws InterruptedException {
lock.lock();
try {
while (queue.size() == maxSize) {
notFull.await();
}
queue.add(message);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
String message = queue.remove();
notFull.signal();
return message;
} finally {
lock.unlock();
}
}
}
在实际项目中,我发现ReentrantLock相比synchronized有几个优势:
- 可中断的锁获取
- 公平锁选项
- 多个Condition支持
- 尝试获取锁的超时机制
3. 高级并发工具类
3.1 CountDownLatch应用场景
CountDownLatch是一种同步辅助工具,允许一个或多个线程等待其他线程完成操作。在电商系统中,我们可以用它来等待所有子系统初始化完成:
java复制public class SystemInitializer {
private static final int SUBSYSTEM_COUNT = 3;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(SUBSYSTEM_COUNT);
new Thread(new CacheInitializer(latch)).start();
new Thread(new DatabaseInitializer(latch)).start();
new Thread(new MQInitializer(latch)).start();
latch.await(); // 等待所有子系统初始化完成
System.out.println("所有系统初始化完成,开始提供服务");
}
static class CacheInitializer implements Runnable {
private final CountDownLatch latch;
CacheInitializer(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
// 模拟初始化过程
try {
Thread.sleep(1000);
System.out.println("缓存系统初始化完成");
latch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 其他Initializer类似...
}
我曾经在一个分布式系统启动优化中使用了CountDownLatch,将系统启动时间从15秒缩短到5秒,因为各子系统可以并行初始化。
3.2 CyclicBarrier使用场景
CyclicBarrier允许一组线程互相等待,直到所有线程都到达某个屏障点。适合并行计算场景:
java复制public class MatrixCalculator {
private final int[][] matrix;
private final int[] results;
private final CyclicBarrier barrier;
public MatrixCalculator(int[][] matrix) {
this.matrix = matrix;
this.results = new int[matrix.length];
this.barrier = new CyclicBarrier(matrix.length, () -> {
System.out.println("所有行计算完成,开始汇总");
int sum = Arrays.stream(results).sum();
System.out.println("矩阵总和: " + sum);
});
}
public void startCalculate() {
for (int i = 0; i < matrix.length; i++) {
final int row = i;
new Thread(() -> {
results[row] = Arrays.stream(matrix[row]).sum();
System.out.println("行 " + row + " 计算完成: " + results[row]);
try {
barrier.await();
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
3.3 Semaphore实战应用
Semaphore用于控制同时访问特定资源的线程数量,非常适合资源池实现:
java复制public class ConnectionPool {
private final Semaphore semaphore;
private final List<Connection> pool = new ArrayList<>();
public ConnectionPool(int poolSize) {
this.semaphore = new Semaphore(poolSize);
for (int i = 0; i < poolSize; i++) {
pool.add(createConnection());
}
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire();
return getAvailableConnection();
}
public void releaseConnection(Connection conn) {
if (conn != null) {
returnConnection(conn);
semaphore.release();
}
}
private synchronized Connection getAvailableConnection() {
// 实现获取可用连接逻辑
return pool.remove(0);
}
private synchronized void returnConnection(Connection conn) {
// 实现归还连接逻辑
pool.add(conn);
}
private Connection createConnection() {
// 创建新连接
return null; // 实际实现返回真实连接
}
}
在一个高并发接口项目中,我使用Semaphore实现了接口限流,防止系统被突发流量击垮。
4. 并发集合与原子操作
4.1 ConcurrentHashMap深入解析
ConcurrentHashMap是线程安全的HashMap实现,相比Hashtable和Collections.synchronizedMap()有更好的并发性能:
java复制public class ProductCache {
private final ConcurrentHashMap<String, Product> cache = new ConcurrentHashMap<>();
public Product getProduct(String id) {
// 使用computeIfAbsent保证原子性
return cache.computeIfAbsent(id, this::loadProductFromDB);
}
public void updateProduct(String id, Product newProduct) {
// 使用replace原子更新
cache.replace(id, newProduct);
}
public void refreshAllProducts() {
// 使用forEach并行遍历
cache.forEach(1, (k, v) ->
cache.replace(k, loadProductFromDB(k)));
}
private Product loadProductFromDB(String id) {
// 数据库加载逻辑
return null; // 实际实现返回真实Product
}
}
ConcurrentHashMap在JDK 8中进行了重大改进,主要优化包括:
- 取消分段锁,改用CAS+synchronized
- 引入红黑树优化哈希冲突性能
- 提供丰富的原子操作方法
4.2 原子类与CAS原理
Java原子类(java.util.concurrent.atomic)基于CAS(Compare-And-Swap)实现,是高性能无锁编程的基础:
java复制public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
// 基本用法
count.incrementAndGet();
// 更复杂的CAS操作
int oldValue, newValue;
do {
oldValue = count.get();
newValue = calculateNewValue(oldValue);
} while (!count.compareAndSet(oldValue, newValue));
}
private int calculateNewValue(int oldValue) {
// 复杂计算逻辑
return oldValue + 1;
}
public int getCount() {
return count.get();
}
}
我曾经在一个高频计数器场景中使用AtomicLong,相比synchronized版本性能提升了近10倍。但要注意ABA问题,必要时可以使用AtomicStampedReference。
5. 线程中断与优雅停止
5.1 正确理解线程中断
线程中断是一种协作机制,用于请求线程停止当前操作。与已废弃的stop()方法不同,它不会强制终止线程:
java复制public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("处理任务中...");
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
System.out.println("收到中断请求,准备退出");
Thread.currentThread().interrupt(); // 重新设置中断状态
break;
}
}
System.out.println("工作线程已停止");
});
worker.start();
Thread.sleep(3000); // 主线程等待3秒
worker.interrupt(); // 请求中断工作线程
}
}
关键点:当捕获InterruptedException时,通常应该要么重新抛出异常,要么调用Thread.currentThread().interrupt()恢复中断状态,而不是简单地忽略它。
5.2 优雅停止线程的最佳实践
在实际项目中,我总结了几种优雅停止线程的模式:
- 使用volatile标志位:
java复制public class StoppableThread implements Runnable {
private volatile boolean running = true;
public void stop() {
running = false;
}
@Override
public void run() {
while (running) {
// 执行任务
}
// 清理资源
}
}
- 使用中断机制:
java复制public class InterruptibleThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 执行可中断的任务
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
- 使用Future和ExecutorService:
java复制ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
});
// 需要停止时
future.cancel(true); // true表示中断正在执行的任务
executor.shutdown();
6. 线程池深度解析
6.1 ThreadPoolExecutor核心参数
ThreadPoolExecutor是Java线程池的核心实现,理解其构造参数对正确使用至关重要:
java复制public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
我曾经在一个Web服务中错误配置了线程池,导致OOM。教训是:
- 合理设置队列容量(避免无界队列)
- 根据业务特点选择合适的拒绝策略
- 监控线程池运行状态
6.2 四种常用线程池对比
Executors工厂类提供了四种常用线程池,但实际项目中建议直接使用ThreadPoolExecutor构造:
| 线程池类型 | 核心特点 | 适用场景 | 潜在问题 |
|---|---|---|---|
| FixedThreadPool | 固定大小线程池 | 已知并发量的稳定负载 | 无界队列可能导致OOM |
| CachedThreadPool | 自动扩容线程池 | 短期异步任务 | 线程数无上限可能导致资源耗尽 |
| SingleThreadExecutor | 单线程执行 | 需要顺序执行的任务 | 无界队列可能导致OOM |
| ScheduledThreadPool | 定时任务线程池 | 周期性任务 | 复杂调度需求可能需要Quartz |
6.3 线程池监控与调优
在实际项目中,我通常会扩展ThreadPoolExecutor来实现监控:
java复制public class MonitorableThreadPool extends ThreadPoolExecutor {
// 记录任务执行时间
private final ConcurrentHashMap<Runnable, Long> startTimes = new ConcurrentHashMap<>();
public MonitorableThreadPool(int corePoolSize, int maxPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
startTimes.put(r, System.currentTimeMillis());
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
long taskTime = System.currentTimeMillis() - startTimes.remove(r);
System.out.println("任务执行时间: " + taskTime + "ms");
super.afterExecute(r, t);
}
@Override
protected void terminated() {
System.out.println("线程池已终止");
super.terminated();
}
}
调优线程池的关键指标:
- 核心线程数:CPU密集型任务建议N+1,IO密集型建议2N
- 最大线程数:根据系统资源和业务特点确定
- 队列选择:短任务用SynchronousQueue,长任务用有界队列
- 拒绝策略:根据业务重要性选择Discard、CallerRuns等策略
7. Java内存模型与并发编程
7.1 happens-before原则详解
happens-before是Java内存模型的核心概念,定义了操作间的可见性规则:
- 程序顺序规则:同一线程中的每个操作happens-before于该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁happens-before于随后对这个锁的加锁
- volatile变量规则:对volatile域的写happens-before于任意后续对这个volatile域的读
- 线程启动规则:Thread.start()的调用happens-before于被启动线程中的任意操作
- 线程终止规则:线程中的任意操作happens-before于其他线程检测到该线程已经终止
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
理解这些规则对编写正确的并发程序至关重要。我曾经遇到一个可见性问题,在没有适当同步的情况下,一个线程修改的标志对另一个线程不可见,最终通过volatile解决了问题。
7.2 volatile关键字深入解析
volatile保证了变量的可见性和禁止指令重排序,但不保证原子性:
java复制public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写volatile变量
}
public void reader() {
if (flag) { // 读volatile变量
// 执行基于flag的操作
}
}
}
volatile的典型使用场景:
- 状态标志(如开关控制)
- 一次性安全发布(如双重检查锁定)
- 独立观察(定期发布观察结果)
但要注意volatile不能替代锁,当需要对变量进行复合操作时(如i++),仍然需要同步。
7.3 final的内存语义
final字段在正确初始化后,对其他线程是可见的,不需要额外同步:
java复制public class FinalFieldExample {
private final int x;
private int y;
public FinalFieldExample() {
x = 42; // 正确初始化final字段
y = 10;
}
public void reader() {
if (x == 42) { // 保证能看到正确初始化的x值
System.out.println("x is properly initialized");
}
// y的值可能看不到,因为不是final
}
}
在并发编程中,应尽可能使用final字段,这可以简化线程安全分析。我曾经重构过一个类,将多个字段改为final后,不仅提高了线程安全性,还使代码更易于理解和维护。
8. 并发设计模式与实践
8.1 不可变对象模式
不可变对象天生线程安全,是最简单的并发设计模式:
java复制public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}
// 只有getter方法,没有setter
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getHobbies() { return hobbies; }
}
在实际项目中,我经常使用不可变对象来表示配置信息、DTO等。它的优点是:
- 线程安全无需同步
- 可以安全共享和缓存
- 简化代码和测试
8.2 线程局部存储模式
ThreadLocal为每个线程提供独立的变量副本,避免共享:
java复制public class UserContextHolder {
private static final ThreadLocal<User> context = new ThreadLocal<>();
public static void setUser(User user) {
context.set(user);
}
public static User getUser() {
return context.get();
}
public static void clear() {
context.remove();
}
}
// 在Web应用中,可以在过滤器中使用
public class UserFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
User user = authenticate(request);
UserContextHolder.setUser(user);
chain.doFilter(request, response);
} finally {
UserContextHolder.clear();
}
}
// ...
}
重要提示:ThreadLocal使用不当会导致内存泄漏,特别是在线程池环境中。一定要在不再需要时调用remove()方法清理。
8.3 生产者-消费者模式进阶实现
使用BlockingQueue实现的生产者-消费者模式既简单又高效:
java复制public class AdvancedProducerConsumer {
private final BlockingQueue<Task> queue;
private final List<Worker> workers;
public AdvancedProducerConsumer(int workerCount) {
this.queue = new LinkedBlockingQueue<>(100); // 有界队列防止OOM
this.workers = new ArrayList<>();
for (int i = 0; i < workerCount; i++) {
Worker worker = new Worker(queue);
workers.add(worker);
new Thread(worker).start();
}
}
public void submitTask(Task task) throws InterruptedException {
queue.put(task); // 阻塞直到队列有空闲
}
public void shutdown() {
workers.forEach(Worker::shutdown);
}
private static class Worker implements Runnable {
private final BlockingQueue<Task> queue;
private volatile boolean running = true;
Worker(BlockingQueue<Task> queue) {
this.queue = queue;
}
public void shutdown() {
running = false;
}
@Override
public void run() {
while (running) {
try {
Task task = queue.poll(1, TimeUnit.SECONDS);
if (task != null) {
task.execute();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
在实际项目中,这种模式非常适合处理异步任务,如订单处理、日志记录等。我曾经用它重构了一个订单处理系统,吞吐量提升了3倍。
9. 并发性能优化与问题排查
9.1 常见并发性能问题
-
锁竞争:过多的线程竞争同一把锁
- 症状:CPU使用率不高但吞吐量低
- 解决方案:减小锁粒度、使用读写锁、无锁数据结构
-
上下文切换过多:线程数远多于CPU核心数
- 症状:系统负载高但CPU使用率不高
- 解决方案:合理设置线程池大小、使用异步IO
-
内存一致性开销:频繁的volatile访问或原子操作
- 症状:CAS操作消耗大量CPU
- 解决方案:批量处理、减少共享变量
9.2 锁优化技巧
- 减小锁粒度:将一个大锁拆分为多个小锁
java复制// 优化前
public class BigLock {
private final Object lock = new Object();
public void method1() {
synchronized(lock) {
// 操作1
}
}
public void method2() {
synchronized(lock) {
// 操作2
}
}
}
// 优化后
public class FineGrainedLock {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized(lock1) {
// 操作1
}
}
public void method2() {
synchronized(lock2) {
// 操作2
}
}
}
- 读写锁分离:ReadWriteLock适合读多写少场景
java复制public class CachedData {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private Object data;
public Object getData() {
rwLock.readLock().lock();
try {
if (data == null) {
// 释放读锁,获取写锁
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try {
// 再次检查,因为可能有其他线程已经修改
if (data == null) {
data = loadDataFromDB();
}
// 降级为读锁
rwLock.readLock().lock();
} finally {
rwLock.writeLock().unlock();
}
}
return data;
} finally {
rwLock.readLock().unlock();
}
}
}
9.3 并发问题排查工具
- JConsole/VisualVM:监控线程状态、锁竞争情况
- Java Mission Control:高级性能分析工具
- jstack:生成线程转储分析死锁
- Arthas:阿里开源的Java诊断工具
我曾经使用jstack发现过一个死锁问题,两个线程互相持有对方需要的锁:
code复制"Thread-1" waiting to lock monitor 0x00007f8b1c0078e8 (object 0x000000076abcec58)
"Thread-2" waiting to lock monitor 0x00007f8b1c007b68 (object 0x000000076abcec68)
解决方案是统一锁的获取顺序,避免循环等待。
10. Java并发编程最佳实践
10.1 并发安全设计原则
- 优先使用不可变对象
- 封装共享状态,限制可变状态的访问
- 使用线程安全的数据结构
- 明确文档记录线程安全保证
- 保持同步区域尽可能小
10.2 性能与安全平衡
- 避免过早优化:先保证正确性,再考虑性能
- 测量而非猜测:使用性能分析工具定位真正瓶颈
- 优先使用高级并发工具(如ConcurrentHashMap)
- 考虑无锁算法(如原子变量)
- 合理设置线程池参数
10.3 常见陷阱与规避
- 锁的范围过大:
java复制// 错误示例
public synchronized void process() {
// 读取配置(不需要同步)
readConfig();
// 核心逻辑(需要同步)
coreOperation();
// 记录日志(不需要同步)
writeLog();
}
// 正确做法
public void process() {
readConfig();
synchronized(this) {
coreOperation();
}
writeLog();
}
- 忽视InterruptedException:
java复制// 错误示例
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 空捕获
}
// 正确做法
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
// 根据业务决定是否退出
}
- 不正确的双重检查锁定:
java复制// 错误示例(JDK5之前)
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 正确做法(使用volatile)
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
Singleton result = instance;
if (result == null) {
synchronized(Singleton.class) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}
}
// 最佳做法(使用静态内部类)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
在实际开发中,我强烈推荐使用静态内部类方式实现单例,它既简单又线程安全,且没有性能开销。