线程基础与生命周期
线程基本概念
线程是程序执行的最小单位,Java中通过Thread
类或实现Runnable
接口来创建线程。每个线程独立执行代码逻辑,其生命周期从start()
方法调用开始,实际执行逻辑定义在run()
方法中。Java 8起还支持无参数且返回void的方法引用作为线程目标。
// 通过继承Thread类创建线程
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 通过Runnable接口创建线程
Runnable task = () -> System.out.println("Runnable running");
Thread thread = new Thread(task);
线程类型与优先级
Java线程分为两类:
- 用户线程(非守护线程):JVM会等待所有用户线程结束后才退出
- 守护线程:不会阻止JVM退出,通常用于后台支持任务
线程优先级是1-10的整数(默认为5),数值越高表示线程越重要。但需注意:
- 优先级仅是给操作系统的调度建议
- 不同平台对优先级的处理可能不同
- 过度依赖优先级可能导致线程饥饿
线程同步与临界区
当多个线程并发访问共享资源时,可能产生竞态条件。需要通过同步机制保护临界区:
// 同步方法
public synchronized void criticalMethod() {
// 临界区代码
}
// 同步代码块
public void criticalSection() {
synchronized(this) {
// 临界区代码
}
}
同步规则:
- 同一时刻只能有一个线程执行对象的同步实例方法
- 同一时刻只能有一个线程执行类的同步静态方法
- 同步会带来性能开销,应尽量减小同步范围
线程状态转换
Java线程生命周期包含6种状态(通过Thread.getState()
获取):
- 新建(NEW):线程对象已创建但未启动
- 可运行(RUNNABLE):线程正在执行或等待CPU资源
- 阻塞(BLOCKED):等待获取监视器锁
- 等待(WAITING):无限期等待其他线程显式唤醒
- 限时等待(TIMED_WAITING):在指定时间内等待
- 终止(TERMINATED):线程执行完毕
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // 进入TIMED_WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
线程控制操作
线程支持以下控制操作,但需谨慎使用:
- interrupt():请求中断线程(需配合中断检查使用)
- stop():强制终止线程(已废弃,不安全)
- suspend()/resume():挂起/恢复线程(已废弃)
正确的中断处理方式:
public void run() {
while(!Thread.currentThread().isInterrupted()) {
// 正常执行逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
break;
}
}
}
并发工具类
Java提供多种高级并发工具简化开发:
- 原子变量:
AtomicInteger
等,保证单个变量操作的原子性 - 显式锁:
ReentrantLock
提供比synchronized更灵活的锁机制 - 执行器框架:
ExecutorService
管理线程池和任务调度 - Fork/Join框架:适用于可分治的任务处理
- 线程局部变量:
ThreadLocal
为每个线程维护独立变量副本
// 原子变量示例
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
// 线程局部变量示例
ThreadLocal dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
这些工具类在保持线程安全的同时,提供了比基础同步机制更高的性能和灵活性。
同步机制与原子操作
原子变量实现无锁线程安全
原子变量(Atomic Variables)通过CAS(Compare-And-Swap)机制实现无锁线程安全操作,适用于计数器、标志位等场景。Java原子包(java.util.concurrent.atomic)提供多种原子类型:
// 原子整型示例
AtomicInteger atomicInt = new AtomicInteger(0);
int newValue = atomicInt.incrementAndGet(); // 原子递增
// 原子引用示例
AtomicReference atomicRef = new AtomicReference<>("initial");
atomicRef.compareAndSet("initial", "updated"); // CAS操作
关键特性:
- 避免显式同步带来的性能损耗
- 提供
getAndUpdate
、accumulateAndGet
等复合操作 - 适合读多写少的高并发场景
显式锁的高级控制
显式锁(Explicit Locks)通过Lock
接口提供比synchronized更精细的控制:
ReentrantLock lock = new ReentrantLock();
try {
lock.lock(); // 可定时、可中断的获取锁
// 临界区代码
} finally {
lock.unlock(); // 必须显式释放
}
核心优势:
- 尝试获取锁:
tryLock()
支持立即返回或超时等待 - 公平性选择:构造时可指定公平/非公平策略
- 条件变量:通过
newCondition()
实现精细线程通信
执行器框架任务调度
执行器框架(Executor Framework)解耦任务提交与执行:
ExecutorService executor = Executors.newFixedThreadPool(4);
Future future = executor.submit(() -> {
TimeUnit.SECONDS.sleep(1);
return 42;
});
// 获取异步结果
Integer result = future.get();
executor.shutdown();
主要组件:
ThreadPoolExecutor
:可配置的核心/最大线程数ScheduledExecutorService
:支持延迟/周期性任务Future
:获取异步计算结果
Fork/Join并行处理
fork/join框架采用工作窃取(work-stealing)算法处理可分解任务:
class FibonacciTask extends RecursiveTask {
final int n;
FibonacciTask(int n) { this.n = n; }
protected Integer compute() {
if (n <= 1) return n;
FibonacciTask f1 = new FibonacciTask(n - 1);
f1.fork(); // 异步执行子任务
return new FibonacciTask(n - 2).compute() + f1.join();
}
}
ForkJoinPool pool = new ForkJoinPool();
int result = pool.invoke(new FibonacciTask(10));
最佳实践:
- 任务应分解为独立子任务
- 避免IO阻塞操作
- 子任务规模应足够大以抵消调度开销
线程局部存储
ThreadLocal实现线程隔离的变量存储:
ThreadLocal dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 每个线程获取独立实例
String date = dateFormat.get().format(new Date());
注意事项:
- 使用后需调用
remove()
防止内存泄漏 - 不适合存储大量数据
- InheritableThreadLocal支持父子线程值继承
同步器对比选型
同步机制 | 适用场景 | 特性 |
---|---|---|
原子变量 | 计数器、状态标志 | 无锁、高性能 |
显式锁 | 复杂同步逻辑 | 可中断、超时控制 |
线程池 | 任务并行处理 | 资源复用、负载均衡 |
Fork/Join | 可分治问题 | 工作窃取、自动并行化 |
这些并发工具通过不同的抽象层次,为开发者提供了从底层原子操作到高级任务调度的完整解决方案。
信号量(Semaphore)应用
信号量基础原理
信号量是一种控制多线程并发访问资源的同步器,与synchronized
单线程限制不同,它允许N个线程同时访问资源(N可为任意正整数)。核心机制通过虚拟许可(permit)实现:
// 创建包含3个许可的信号量
Semaphore semaphore = new Semaphore(3);
当N=1时,信号量可模拟synchronized
的互斥访问特性。信号量维护的许可数量决定了可同时访问资源的线程数。
关键操作方法
- acquire():获取许可(若无可用许可则阻塞)
- release():释放许可(增加可用许可数)
- tryAcquire():尝试获取许可(立即返回成功/失败)
- availablePermits():查询当前可用许可数
// 典型使用模式
semaphore.acquire();
try {
// 访问受保护资源
} finally {
semaphore.release();
}
公平性控制
信号量支持公平/非公平两种模式,通过构造器指定:
// 创建公平信号量(保证FIFO获取顺序)
Semaphore fairSemaphore = new Semaphore(3, true);
公平信号量能避免线程饥饿,但会带来额外的性能开销。非公平信号量(默认)通常具有更高的吞吐量。
餐厅餐桌分配案例
以下代码模拟餐厅餐桌管理系统,演示信号量实际应用:
class Restaurant {
private final Semaphore tables;
public Restaurant(int tableCount) {
this.tables = new Semaphore(tableCount);
}
public void getTable(int customerId) throws InterruptedException {
System.out.println("顾客#" + customerId + "等待餐桌...");
tables.acquire();
System.out.println("顾客#" + customerId + "获得餐桌");
}
public void returnTable(int customerId) {
System.out.println("顾客#" + customerId + "归还餐桌");
tables.release();
}
}
class Customer extends Thread {
private final Restaurant restaurant;
private final int id;
public Customer(Restaurant r, int id) {
this.restaurant = r;
this.id = id;
}
@Override
public void run() {
try {
restaurant.getTable(id);
Thread.sleep((int)(Math.random() * 10000)); // 模拟用餐时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
restaurant.returnTable(id);
}
}
}
注意事项
- 许可释放次数可超过初始数量(通过多次调用
release()
) - 一个线程获取的许可可由其他线程释放
- 必须确保
release()
在finally块中调用,避免资源泄漏 - 支持批量获取/释放许可:
semaphore.acquire(2); // 获取2个许可 semaphore.release(3); // 释放3个许可
信号量特别适用于资源池管理、流量控制等场景,相比synchronized
提供更灵活的并发控制能力。
屏障(Barrier)与相位器(Phaser)
CyclicBarrier线程同步机制
CyclicBarrier使一组线程在屏障点(barrier point)同步等待,当最后一个线程到达后,所有线程才会继续执行。与CountDownLatch不同,CyclicBarrier可重复使用,特别适用于迭代算法中的多阶段计算。
// 创建5线程的屏障,并定义屏障动作
Runnable barrierAction = () -> System.out.println("所有线程到达屏障点");
CyclicBarrier barrier = new CyclicBarrier(5, barrierAction);
class Worker extends Thread {
private final CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
// 执行第一阶段工作
System.out.println(getName() + "完成阶段1");
barrier.await(); // 等待其他线程
// 所有线程到达后继续执行
System.out.println(getName() + "开始阶段2");
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
}
关键特性:
await()
方法返回到达索引(最后到达的线程返回0)- 支持超时机制:
await(long timeout, TimeUnit unit)
- 屏障被破坏时抛出BrokenBarrierException
- 重置功能:
reset()
方法可重新使用屏障
Phaser动态线程协调
Phaser提供比CyclicBarrier更灵活的同步控制,主要特点包括:
- 动态注册/注销:支持运行时调整参与线程数
- 分阶段推进:每轮同步后phase值递增(超过Integer.MAX_VALUE后归零)
- 分层结构:支持树形phaser结构减少竞争
- 可终止性:通过onAdvance()控制终止条件
class Task extends Thread {
private final Phaser phaser;
public Task(String name, Phaser phaser) {
super(name);
this.phaser = phaser;
phaser.register(); // 动态注册
}
@Override
public void run() {
do {
// 执行阶段任务
System.out.println(getName() + "完成阶段"+phaser.getPhase());
phaser.arriveAndAwaitAdvance();
} while (!phaser.isTerminated());
}
}
// 使用示例
Phaser phaser = new Phaser(1); // 初始注册1个控制线程
for(int i=0; i<3; i++) {
new Task("Worker-"+i, phaser).start();
}
phaser.arriveAndDeregister(); // 控制线程退出
相位器高级功能
自定义相位切换动作
通过继承Phaser并重写onAdvance()方法实现:
class CustomPhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int parties) {
System.out.println("阶段"+phase+"完成,参与方:"+parties);
return phase >= 3 || parties == 0; // 超过3阶段或无双注册方时终止
}
}
多阶段任务协同
Phaser特别适合多阶段并行计算场景:
class ProcessingTask extends Thread {
private final Phaser phaser;
private final List data;
public ProcessingTask(Phaser p, List data) {
this.phaser = p;
this.data = data;
p.register();
}
@Override
public void run() {
// 阶段1:数据加载
loadData();
phaser.arriveAndAwaitAdvance();
// 阶段2:数据处理
processData();
phaser.arriveAndAwaitAdvance();
// 阶段3:结果汇总
saveResults();
phaser.arriveAndDeregister();
}
// ...具体方法实现...
}
对比选型建议
特性 | CyclicBarrier | Phaser |
---|---|---|
参与方数量 | 固定 | 动态可调 |
重用性 | 支持reset() | 自动推进相位 |
分层结构 | 不支持 | 支持 |
终止控制 | 无内置机制 | 通过onAdvance()控制 |
适用场景 | 简单多线程同步 | 复杂分阶段任务协调 |
实际开发中,Phaser更适合以下场景:
- 需要动态增减参与线程的并行任务
- 多阶段流水线处理系统
- 需要精细控制相位切换逻辑的应用
- 大规模并行计算中的任务协调
注意事项:
- 避免在phaser未终止时长时间阻塞
- 确保正确匹配register()和arriveAndDeregister()调用
- 在树形结构中合理分配子phaser的负载
闭锁(Latch)与交换器(Exchanger)
CountDownLatch一次性同步
CountDownLatch是一种不可重置的同步辅助类,允许一个或多个线程等待直到在其他线程中执行的一组操作完成。典型应用场景包括:
- 服务启动依赖:确保所有前置服务就绪后再启动主服务
- 并行任务初始化:等待所有工作线程完成初始化后再处理数据
// 创建初始计数为2的闭锁
CountDownLatch latch = new CountDownLatch(2);
class Service extends Thread {
private final CountDownLatch latch;
public Service(String name, CountDownLatch latch) {
super(name);
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(getName() + "正在启动...");
Thread.sleep((int)(Math.random() * 3000));
System.out.println(getName() + "启动完成");
latch.countDown(); // 计数减1
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 主线程等待服务启动
new Service("数据库服务", latch).start();
new Service("缓存服务", latch).start();
latch.await(); // 阻塞直到计数归零
System.out.println("所有依赖服务就绪,开始启动主应用");
关键特性:
- 计数不可重置,达到零后所有等待线程立即释放
countDown()
可在不同线程调用- 支持超时等待:
await(long timeout, TimeUnit unit)
Exchanger线程数据交换
Exchanger实现两个线程间的双向数据交换屏障,适用于生产者-消费者模式中的缓冲区交换:
Exchanger> exchanger = new Exchanger<>();
class Producer extends Thread {
private List buffer = new ArrayList<>();
private final Exchanger> exchanger;
public Producer(Exchanger> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
while (!Thread.interrupted()) {
// 生产数据
for (int i = 0; i < 5; i++) {
buffer.add(i);
}
System.out.println("生产者已填充缓冲区");
try {
// 交换缓冲区
buffer = exchanger.exchange(buffer);
Thread.sleep(1000); // 模拟处理延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
class Consumer extends Thread {
private List buffer = new ArrayList<>();
private final Exchanger> exchanger;
public Consumer(Exchanger> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
while (!Thread.interrupted()) {
try {
// 获取生产者数据
buffer = exchanger.exchange(buffer);
System.out.println("消费者收到数据量: " + buffer.size());
// 清空缓冲区返回给生产者
buffer.clear();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
实现要点:
- 交换操作是同步的,双方线程会互相阻塞直到都到达交换点
- 支持泛型数据交换,但类型必须匹配
- 可配置超时机制避免永久等待
同步器对比
特性 | CountDownLatch | Exchanger |
---|---|---|
参与方数量 | 多对一(等待计数归零) | 严格一对一 |
重用性 | 不可重置 | 可重复交换 |
数据传递 | 无 | 支持双向数据交换 |
典型应用场景 | 服务启动依赖 | 生产者-消费者缓冲区交换 |
注意事项:
- CountDownLatch的
countDown()
应放在finally块确保执行 - Exchanger交换的数据对象应是线程安全的
- 两者都不支持中断的线程恢复操作,需自行处理中断状态
Java多线程编程总结
完整工具链与适用场景
Java提供了从基础线程管理到高级同步器的完整多线程编程工具链。不同同步器针对特定场景设计:
- 信号量(Semaphore):精确控制资源并发访问量
- 屏障(CyclicBarrier):协调多阶段并行任务
- 闭锁(CountDownLatch):处理服务启动等一次性依赖
- 交换器(Exchanger):实现线程间安全数据交换
// 典型同步器使用模式对比
Semaphore semaphore = new Semaphore(10); // 限流10并发
CyclicBarrier barrier = new CyclicBarrier(5); // 5线程同步点
CountDownLatch latch = new CountDownLatch(3); // 3次计数
Exchanger exchanger = new Exchanger<>(); // 数据交换
线程安全核心考量
实现线程安全需要根据场景合理选择方案:
- 原子变量:适用于简单计数器等场景
AtomicLong counter = new AtomicLong(0); counter.getAndIncrement();
- 显式锁:复杂同步逻辑时提供更精细控制
Lock lock = new ReentrantLock(); lock.lockInterruptibly(); // 可中断获取锁
- 线程封闭:通过ThreadLocal实现线程隔离
ThreadLocal connHolder = ThreadLocal.withInitial( () -> DriverManager.getConnection(DB_URL));
线程状态管理
理解线程状态转换对问题诊断至关重要:
状态 | 触发条件 | 转换方法 |
---|---|---|
NEW | 线程刚创建 | Thread.start() |
RUNNABLE | 可执行状态 | 获取CPU时间片 |
BLOCKED | 等待监视器锁 | synchronized块竞争 |
WAITING | 无限期等待 | Object.wait()/join() |
TIMED_WAITING | 有限期等待 | Thread.sleep(n) |
TERMINATED | 执行完毕 | run()方法结束 |
// 线程状态监控示例
Thread thread = new Thread(task);
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
现代并发框架优势
Java 5+引入的并发框架大幅简化了复杂并行任务实现:
- ExecutorService:线程池统一管理
ExecutorService exec = Executors.newWorkStealingPool(); Future future = exec.submit(() -> 42);
- ForkJoinPool:自动任务分解与工作窃取
class SumTask extends RecursiveTask { protected Long compute() { // 任务分解逻辑 } }
- CompletableFuture:异步编程组合
CompletableFuture.supplyAsync(() -> "data") .thenApply(String::toUpperCase) .thenAccept(System.out::println);
最佳实践建议
- 优先使用高层抽象(如并发集合、Executor)
- 避免过度同步,减小临界区范围
- 使用线程池代替直接创建线程
- 注意死锁、活锁和资源饥饿问题
- 多使用不可变对象和线程安全容器
// 线程安全集合示例
ConcurrentMap map = new ConcurrentHashMap<>();
map.compute("key", (k,v) -> v == null ? 1 : v+1);
// 不可变对象示例
final class ImmutablePoint {
private final int x, y;
// 构造后状态不可变
}
Java多线程体系既提供了底层控制能力,又通过高级抽象简化了并发编程。开发者应当根据具体需求选择合适的并发工具,在保证线程安全的前提下优化性能。