Java多线程终极指南:从基础到高级应用
一、多线程基础概念
1.1 进程与线程的区别
对比维度 进程(Process) 线程(Thread) 定义 操作系统资源分配的基本单位 CPU调度的基本单位 内存空间 独立内存空间 共享所属进程的内存空间 通信方式 进程间通信(IPC)较复杂 可直接读写共享变量 创建开销 大(需要分配独立资源) 小(共享进程资源) 稳定性 一个进程崩溃不影响其他进程 一个线程崩溃可能导致整个进程退出
进程(Process)
在Java中,进程是操作系统资源分配的基本单位,具有独立的内存空间。每个Java应用程序运行时都至少有一个进程。进程特点包括:
- 独立性:拥有独立的地址空间、数据栈等
- 资源开销大:创建和销毁需要较多系统资源
- 通信复杂:进程间通信(IPC)需要特殊机制(如管道、套接字等)
线程(Thread)
线程是Java并发编程的基本执行单元,是进程内的一个独立执行流。特点包括:
- 共享进程资源:同一进程内的线程共享堆内存和方法区
- 轻量级:创建和切换开销远小于进程
- 通信简单:可通过共享变量直接通信
- Java通过java.lang.Thread类和Runnable接口实现多线程
进程 vs 线程:餐厅比喻
想象一家餐厅:
- 进程就像整个餐厅,有独立的厨房(内存)、收银台(资源)
- 线程就像餐厅里的服务员,多个服务员共享同一个厨房和收银台
1.2 为什么需要多线程
- 提高CPU利用率:当线程I/O阻塞时,其他线程可以继续使用CPU
- 更快的响应:GUI程序使用单独线程处理用户输入
- 简化建模:每个线程处理单一任务,代码更清晰
- 多核优势:现代CPU多核心可真正并行执行线程
日常例子:浏览器同时下载多个文件(每个下载任务一个线程),同时还能响应用户操作(UI线程)。
二、Java线程创建与管理
2.1 创建线程的三种方式
方式1:继承Thread类
// 自定义线程类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
方式2:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable线程: " + Thread.currentThread().getName());
}
}
public class RunnableDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
方式3:使用Callable和Future(可获取返回值)
import java.util.concurrent.*;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Callable结果";
}
}
public class CallableDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println("等待结果...");
String result = future.get(); // 阻塞直到获取结果
System.out.println("获取结果: " + result);
executor.shutdown();
}
}
2.2 三种创建方式对比
对比点 继承Thread类 实现Runnable接口 实现Callable接口 返回值 无 无 有 异常处理 只能在run()内处理 只能在run()内处理 可以通过Future获取 单继承限制 受限于Java单继承 不受限 不受限 线程池支持 不支持 支持 支持 适用场景 简单线程任务 推荐方式 需要返回结果的场景
建议:优先选择实现Runnable接口或Callable接口的方式,避免继承的局限性。
三、线程生命周期与状态转换
3.1 线程的6种状态
Java线程在生命周期中有6种状态(定义在Thread.State枚举中):
- **NEW(新建)**:线程被创建但尚未启动
- **RUNNABLE(可运行)**:线程正在JVM中执行或等待操作系统资源
- **BLOCKED(阻塞)**:等待监视器锁(进入synchronized块)
- **WAITING(等待)**:无限期等待其他线程执行特定操作(如wait())
- **TIMED_WAITING(计时等待)**:有限时间等待(如sleep())
- **TERMINATED(终止)**:线程执行完毕
3.2 状态转换图
NEW ---start()---> RUNNABLE
RUNNABLE ---获取锁---> BLOCKED
RUNNABLE ---wait()---> WAITING
RUNNABLE ---sleep()---> TIMED_WAITING
WAITING ---notify()---> RUNNABLE
TIMED_WAITING ---时间到---> RUNNABLE
RUNNABLE ---run()结束---> TERMINATED
代码示例观察状态:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("新建状态: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后状态: " + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("sleep时状态: " + thread.getState()); // TIMED_WAITING
thread.join();
System.out.println("结束后状态: " + thread.getState()); // TERMINATED
}
}
四、线程同步与锁机制
4.1 同步问题的产生
概念:当多个线程访问共享资源时,由于线程调度的不确定性,可能导致:
- 竞态条件(Race Condition):执行结果依赖于线程执行的时序
- 内存可见性问题:线程对共享变量的修改对其他线程不可见
- 指令重排序:编译器和处理器优化导致的执行顺序改变
例如:这就像你和室友共用一个冰箱:
- 你看到最后一瓶可乐(检查条件)
- 你伸手去拿(执行操作)
- 同时你室友也伸手
- 结果要么:1) 你俩各拿到半瓶 2) 系统崩溃 3) 可乐凭空消失
经典问题:银行取款问题
class BankAccount {
private int balance = 1000;
public void withdraw(int amount) {
if (balance >= amount) {
try {
Thread.sleep(10); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + "取款" + amount + ",余额: " + balance);
} else {
System.out.println("余额不足");
}
}
}
public class BankDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 两个线程同时取款
new Thread(() -> account.withdraw(800), "线程1").start();
new Thread(() -> account.withdraw(800), "线程2").start();
}
}
输出可能:
线程1取款800,余额: 200
线程2取款800,余额: -600
4.2 同步解决方案
方案1:synchronized关键字
特性:
- 内置锁(Intrinsic Lock)/监视器锁(Monitor Lock)
- 保证原子性(atomicity)和可见性(visibility)
- 可重入性(Reentrancy):线程可以重复获取已持有的锁
- 方法级和代码块级两种使用方式
流程:
- 线程到达同步代码:"我要进这个房间"
- JVM门神:"请出示你的锁对象身份证"
- 如果没人占用:"请进,记得出来时敲门"
- 如果已被占用:"门口排队,别踢门!"
public class house {
private final Object lock = new Object();
public void use() {
synchronized(lock) { // 获取锁
// 临界区代码
System.out.println("正在使用中...");
} // 释放锁
}
public synchronized void clean() { // 方法级同步
System.out.println("保洁阿姨工作中");
}
}
方案2:ReentrantLock
特性:
- 可重入
- 可中断(lockInterruptibly)
- 尝试获取锁(tryLock)
- 公平/非公平模式
import java.util.concurrent.locks.ReentrantLock;
class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private int balance = 1000;
public void withdraw(int amount) {
lock.lock(); // 加锁
try {
if (balance >= amount) {
Thread.sleep(10);
balance -= amount;
System.out.println(Thread.currentThread().getName() + "取款" + amount + ",余额: " + balance);
} else {
System.out.println("余额不足");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 确保释放锁
}
}
}
4.3 synchronized与ReentrantLock对比
对比点 synchronized ReentrantLock 实现机制 JVM层面实现 JDK代码实现 锁获取方式 自动获取释放 需要手动lock/unlock 灵活性 相对不灵活 可尝试获取锁、定时锁、公平锁等 性能 优化后性能接近 高竞争下性能更好 中断响应 不支持 支持lockInterruptibly() 条件队列 单一 可创建多个Condition 适用场景 简单同步需求 复杂同步控制
五、线程间通信
5.1 wait/notify机制
生产者-消费者模型:
class MessageQueue {
private String message;
private boolean empty = true;
public synchronized String take() {
while (empty) {
try {
wait(); // 等待消息
} catch (InterruptedException e) {}
}
empty = true;
notifyAll(); // 通知生产者
return message;
}
public synchronized void put(String message) {
while (!empty) {
try {
wait(); // 等待消费
} catch (InterruptedException e) {}
}
empty = false;
this.message = message;
notifyAll(); // 通知消费者
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue();
// 生产者
new Thread(() -> {
String[] messages = {"消息1", "消息2", "消息3"};
for (String msg : messages) {
queue.put(msg);
System.out.println("生产: " + msg);
}
}).start();
// 消费者
new Thread(() -> {
for (int i = 0; i < 3; i++) {
String msg = queue.take();
System.out.println("消费: " + msg);
}
}).start();
}
}
5.2 Condition接口
import java.util.concurrent.locks.*;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待不满
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 通知不空
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待不空
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 通知不满
return x;
} finally {
lock.unlock();
}
}
}
六、线程池与Executor框架
6.1 为什么使用线程池
- 降低资源消耗:重复利用已创建的线程
- 提高响应速度:任务到达时线程已存在
- 提高线程可管理性:统一分配、调优和监控
- 防止资源耗尽:限制最大线程数
6.2 线程池核心参数
参数 说明 corePoolSize 核心线程数,即使空闲也不会被回收 maximumPoolSize 最大线程数,当工作队列满时创建新线程直到达到此数量 keepAliveTime 非核心线程空闲存活时间 unit 存活时间单位 workQueue 工作队列,保存等待执行的任务 threadFactory 线程工厂,用于创建新线程 handler 拒绝策略,当线程池和工作队列都满时如何处理新任务
6.3 四种常见线程池
// 1. 固定大小线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 2. 单线程池(保证顺序执行)
ExecutorService singleThread = Executors.newSingleThreadExecutor();
// 3. 缓存线程池(自动扩容)
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 4. 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
6.4 自定义线程池示例
public class ThreadPoolDemo {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 空闲时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), // 任务队列容量2
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交10个任务
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("执行任务 " + taskId + ",线程: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
6.5 线程池拒绝策略
策略 行为 AbortPolicy 默认策略,直接抛出RejectedExecutionException CallerRunsPolicy 用调用者所在线程来执行任务 DiscardPolicy 直接丢弃任务,不做任何处理 DiscardOldestPolicy 丢弃队列中最旧的任务,然后尝试提交当前任务
七、高级并发工具类
7.1 CountDownLatch
应用场景:多个线程等待直到所有前置操作完成
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // 需要计数3次
new Thread(() -> {
System.out.println("任务1完成");
latch.countDown(); // 计数减1
}).start();
new Thread(() -> {
System.out.println("任务2完成");
latch.countDown();
}).start();
new Thread(() -> {
System.out.println("任务3完成");
latch.countDown();
}).start();
latch.await(); // 等待计数归零
System.out.println("所有任务完成,继续主线程");
}
}
7.2 CyclicBarrier
应用场景:一组线程互相等待到达屏障点
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障,执行屏障动作");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "到达屏障");
try {
barrier.await(); // 等待其他线程
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "继续执行");
}).start();
}
}
}
7.3 Semaphore
应用场景:控制同时访问特定资源的线程数量
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + "获得许可,执行中...");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放许可");
semaphore.release(); // 释放许可
}
}, "线程" + i).start();
}
}
}
7.4 并发工具对比
工具类 作用 关键方法 可重用性 CountDownLatch 一个或多个线程等待其他线程完成操作 countDown(), await() 否 CyclicBarrier 一组线程互相等待到达屏障点 await() 是 Semaphore 控制同时访问特定资源的线程数量 acquire(), release() 是 Phaser 更灵活的屏障,可以动态注册和注销参与方 arrive(), awaitAdvance() 是
八、原子变量与CAS
8.1 原子操作类
Java提供了一系列原子变量类,如AtomicInteger, AtomicLong, AtomicReference等。
public class AtomicDemo {
public static void main(String[] args) throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet(); // 原子递增
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数: " + counter.get()); // 总是2000
}
}
8.2 CAS原理
CAS(Compare And Swap)是原子变量的实现原理,包含三个操作数:
- 内存位置(V)
- 预期原值(A)
- 新值(B)
当且仅当V的值等于A时,处理器才会用B更新V的值,否则不执行更新。
ABA问题:虽然值还是A,但可能已经被修改过又改回来了。解决方案:使用AtomicStampedReference带版本号。
九、并发集合类
9.1 常用并发集合
接口 非线程安全实现 线程安全实现 List ArrayList CopyOnWriteArrayList Set HashSet CopyOnWriteArraySet, ConcurrentSkipListSet Map HashMap ConcurrentHashMap, ConcurrentSkipListMap Queue LinkedList ArrayBlockingQueue, LinkedBlockingQueue Deque ArrayDeque LinkedBlockingDeque
9.2 ConcurrentHashMap示例
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 多个线程并发写入
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
for (int j = 0; j < 100; j++) {
String key = "key-" + taskId + "-" + j;
map.put(key, j);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Map大小: " + map.size());
}
}
9.3 CopyOnWriteArrayList示例
public class CopyOnWriteDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 一个线程迭代
new Thread(() -> {
list.add("A");
list.add("B");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println("迭代: " + it.next());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 另一个线程修改
new Thread(() -> {
try {
Thread.sleep(500);
list.add("C");
System.out.println("添加了C");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
十、Java内存模型(JMM)与happens-before
10.1 JMM核心概念
Java内存模型定义了线程如何与内存交互,主要解决以下问题:
- 原子性:基本读写操作是原子的
- 可见性:一个线程修改对另一个线程可见
- 有序性:防止指令重排序
10.2 happens-before原则
- 程序顺序规则:同一线程中的操作,前面的happens-before后面的
- 锁规则:解锁happens-before后续加锁
- volatile规则:写happens-before后续读
- 线程启动规则:线程start()happens-before它的任何操作
- 线程终止规则:线程的所有操作happens-before它的终止检测
- 中断规则:调用interrupt()happens-before检测到中断
- 终结器规则:对象构造happens-before它的finalize()
- 传递性:A hb B,B hb C => A hb C
10.3 volatile关键字
public class VolatileDemo {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// 工作代码
}
System.out.println("线程停止");
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
new Thread(demo::run).start();
Thread.sleep(1000);
demo.stop();
}
}
十一、实战案例分析
11.1 高性能计数器
public class HighPerformanceCounter {
private final AtomicLong counter = new AtomicLong(0);
private final LongAdder fastCounter = new LongAdder();
// 简单原子计数器
public void incrementAtomic() {
counter.incrementAndGet();
}
// 高并发优化计数器
public void incrementAdder() {
fastCounter.increment();
}
public long getAtomicCount() {
return counter.get();
}
public long getAdderCount() {
return fastCounter.sum();
}
public static void main(String[] args) throws InterruptedException {
HighPerformanceCounter counter = new HighPerformanceCounter();
ExecutorService executor = Executors.newFixedThreadPool(8);
long start = System.currentTimeMillis();
// 测试AtomicLong性能
for (int i = 0; i < 1000000; i++) {
executor.execute(counter::incrementAtomic);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
long atomicTime = System.currentTimeMillis() - start;
executor = Executors.newFixedThreadPool(8);
start = System.currentTimeMillis();
// 测试LongAdder性能
for (int i = 0; i < 1000000; i++) {
executor.execute(counter::incrementAdder);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
long adderTime = System.currentTimeMillis() - start;
System.out.println("AtomicLong结果: " + counter.getAtomicCount() + ", 耗时: " + atomicTime + "ms");
System.out.println("LongAdder结果: " + counter.getAdderCount() + ", 耗时: " + adderTime + "ms");
}
}
11.2 限流器实现
public class RateLimiter {
private final Semaphore semaphore;
private final int maxPermits;
private final long period;
private ScheduledExecutorService scheduler;
public RateLimiter(int permits, long period, TimeUnit unit) {
this.semaphore = new Semaphore(permits);
this.maxPermits = permits;
this.period = unit.toMillis(period);
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
int current = semaphore.availablePermits();
if (current < maxPermits) {
semaphore.release(maxPermits - current);
}
}, 0, this.period, TimeUnit.MILLISECONDS);
}
public boolean tryAcquire() {
return semaphore.tryAcquire();
}
public void acquire() throws InterruptedException {
semaphore.acquire();
}
public void shutdown() {
scheduler.shutdown();
}
public static void main(String[] args) throws InterruptedException {
// 每秒最多5个请求
RateLimiter limiter = new RateLimiter(5, 1, TimeUnit.SECONDS);
// 模拟10个请求
for (int i = 1; i <= 10; i++) {
if (limiter.tryAcquire()) {
System.out.println("处理请求 " + i);
} else {
System.out.println("限流请求 " + i);
}
Thread.sleep(100);
}
limiter.shutdown();
}
}
十二、常见问题与最佳实践
12.1 多线程常见问题
- 死锁:多个线程互相等待对方释放锁
- 避免方法:按固定顺序获取锁,使用tryLock()设置超时
- 活锁:线程不断改变状态但无法继续执行
- 避免方法:引入随机性,如随机等待时间
- 线程饥饿:某些线程长期得不到执行
- 解决方法:使用公平锁,合理设置线程优先级
12.2 最佳实践
- 尽量使用高层并发工具:如线程池、并发集合
- 优先使用不可变对象:避免同步问题
- 缩小同步范围:只同步必要的代码块
- 避免过早优化:先保证正确性,再考虑性能
- 考虑使用并行流:Java 8+的parallelStream()
- 合理设置线程池大小:
- CPU密集型:CPU核心数+1
- IO密集型:CPU核心数 × (1 + 平均等待时间/平均计算时间)
12.3 性能调优建议
- 减少锁竞争:
- 缩小同步块
- 使用读写锁(ReentrantReadWriteLock)
- 使用分段锁(如ConcurrentHashMap)
- 避免上下文切换:
- 合理设置线程数
- 使用协程(如Quasar库)
- 使用无锁数据结构:
- Atomic类
- LongAdder
- ConcurrentLinkedQueue
总结
Java多线程编程是Java高级开发的核心技能之一。本文从基础概念到高级应用,全面介绍了Java多线程的各个方面:
- 线程创建与生命周期管理
- 同步机制与锁优化
- 线程间通信方式
- 线程池与Executor框架
- 高级并发工具类
- 原子变量与CAS
- 并发集合类
- Java内存模型
- 实战案例与最佳实践
Java 多线程像一群疯跑的小怪兽,协调好就齐力通关,没控制住,程序直接被它们折腾得 “脑震荡”!
★ 收藏转发的人,2024年必暴富!——来自一位贫穷但真诚的博主。