多线程基础
一、线程创建方式
Java 中创建线程主要有三种方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口配合 FutureTask。
1. 继承 Thread 类
直接继承 Thread 类,重写 run() 方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行中: " + Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程
// 匿名内部类方式
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("匿名线程: " + Thread.currentThread().getName());
}
};
t2.start();
}
}说明:调用 start() 才会启动新线程,直接调用 run() 只是普通方法调用,不会创建新线程。Java 是单继承机制,继承 Thread 后无法再继承其他类,灵活性较差。
2. 实现 Runnable 接口
实现 Runnable 接口,重写 run() 方法,再传给 Thread 构造器。
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("任务执行中: " + Thread.currentThread().getName());
}
}
public class RunnableDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask());
t1.start();
// Lambda 表达式方式(Runnable 是函数式接口)
Thread t2 = new Thread(() -> {
System.out.println("Lambda 线程: " + Thread.currentThread().getName());
});
t2.start();
// 多个线程共享同一个任务实例
MyTask task = new MyTask();
new Thread(task, "线程-A").start();
new Thread(task, "线程-B").start();
}
}说明:相比继承 Thread,实现 Runnable 接口更灵活,因为 Java 支持实现多个接口。适合多个线程共享同一任务实例的场景。
3. 实现 Callable + FutureTask
Callable 与 Runnable 的区别在于:Callable 可以返回结果,且能抛出异常。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ExecutionException;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 模拟耗时计算
Thread.sleep(1000);
return "返回结果: " + Thread.currentThread().getName();
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask, "计算线程");
t.start();
// 主线程可以干别的事
System.out.println("主线程继续工作...");
// 获取计算结果(get() 会阻塞直到任务完成)
String result = futureTask.get();
System.out.println("获取到结果: " + result);
}
}说明:FutureTask 是 RunnableFuture 接口的实现类,它既实现了 Runnable(可传给 Thread),又实现了 Future(可获取返回结果)。get() 方法会阻塞当前线程直到任务执行完毕。
二、线程生命周期
Java 中的线程在任意时刻都处于以下 6 种状态之一。
状态枚举
public class LifecycleDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("1. 刚创建: " + t.getState()); // NEW
t.start();
System.out.println("2. 启动后: " + t.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("3. 休眠中: " + t.getState()); // TIMED_WAITING
t.join();
System.out.println("4. 结束后: " + t.getState()); // TERMINATED
}
}6 种状态详解
| 状态 | 描述 | 触发场景 |
|---|---|---|
| NEW | 线程刚创建,尚未启动 | new Thread() 之后 |
| RUNNABLE | 线程正在运行或等待 CPU 调度 | start() 之后 |
| BLOCKED | 线程被阻塞,等待监视器锁 | 等待进入 synchronized 块 |
| WAITING | 线程无限期等待另一个线程执行特定操作 | wait()、join()、park() |
| TIMED_WAITING | 线程在指定时间内等待 | sleep(ms)、wait(ms)、join(ms) |
| TERMINATED | 线程执行完毕 | run() 方法结束 |
状态流转图
┌──────────────────────────────────────────┐
│ │
▼ │
NEW ──start()──▶ RUNNABLE ──run()结束──▶ TERMINATED │
│ ▲ │
│ │ │
获取锁失败 │ │ 锁可用/被唤醒 │
│ │ │
▼ │ │
BLOCKED │
│ ▲ │
│ │ │
wait() ─────┘ │ notify()/notifyAll() │
join() │ │
park() │ │
▼ │ │
WAITING │
│ ▲ │
│ │ │
sleep(ms) ───┘ │ timeout 到达/被唤醒 │
wait(ms) │ │
join(ms) │ │
parkNanos() │ │
▼ │ │
TIMED_WAITING │
│ │
└───────────────────────────────────────────┘说明:RUNNABLE 状态包含两个子状态:Running(正在执行)和 Ready(等待 CPU 调度),JVM 层面统称为 RUNNABLE。操作系统层面的"阻塞等待 I/O"在 JVM 中仍属于 RUNNABLE。
三、synchronized 关键字
1. 对象锁 vs 类锁
public class SyncDemo {
// 实例方法加锁 → 锁的是当前对象(对象锁)
public synchronized void instanceMethod() {
System.out.println(Thread.currentThread().getName() + " 获取了对象锁");
sleep(2000);
}
// 静态方法加锁 → 锁的是类对象(类锁)
public static synchronized void staticMethod() {
System.out.println(Thread.currentThread().getName() + " 获取了类锁");
sleep(2000);
}
// 同步代码块 - 锁对象
public void blockLockObject() {
synchronized (this) { // 锁当前对象
System.out.println(Thread.currentThread().getName() + " 进入同步块(对象锁)");
sleep(2000);
}
}
// 同步代码块 - 锁类
public void blockLockClass() {
synchronized (SyncDemo.class) { // 锁类对象
System.out.println(Thread.currentThread().getName() + " 进入同步块(类锁)");
sleep(2000);
}
}
private static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); }
}
public static void main(String[] args) {
SyncDemo demo = new SyncDemo();
// 对象锁:两个线程访问同一对象的同步方法,会互斥
new Thread(demo::instanceMethod, "线程-1").start();
new Thread(demo::instanceMethod, "线程-2").start();
// 输出:线程-1 获取了对象锁 →(等2秒)→ 线程-2 获取了对象锁
// 类锁:两个线程访问静态同步方法,会互斥
new Thread(SyncDemo::staticMethod, "线程-3").start();
new Thread(SyncDemo::staticMethod, "线程-4").start();
// 对象锁与类锁不互斥!它们锁的是不同的对象
new Thread(demo::instanceMethod, "线程-对象").start();
new Thread(SyncDemo::staticMethod, "线程-类").start();
// 输出:两个线程几乎同时执行,不需要等待
}
}总结:
| 锁类型 | 锁的对象 | 作用域 |
|---|---|---|
| 对象锁 | this 或指定实例 | 实例方法 / synchronized(this) 块 |
| 类锁 | .class 类对象 | 静态方法 / synchronized(Xxx.class) 块 |
对象锁和类锁互不干扰,因为它们锁的是不同的对象。
2. synchronized 底层原理(Monitor)
synchronized 在 JVM 层面是通过 Monitor(监视器锁) 实现的。
- 同步代码块:编译后会在前后插入
monitorenter和monitorexit指令。 - 同步方法:通过方法表的
ACC_SYNCHRONIZED标志来隐式获取 Monitor。
Monitor 的工作原理:
每个 Java 对象在内存中都与一个 Monitor 关联(由 ObjectMonitor 实现)。当线程执行到 monitorenter 时,会尝试获取对象的 Monitor 所有权:
- Monitor 的计数器初始为 0,表示未加锁。
- 线程 A 执行
monitorenter,将计数器置为 1,成为 Monitor 的持有者。 - 线程 B 尝试获取同一 Monitor,发现计数器为 1,被阻塞进入
_EntryList队列。 - 线程 A 执行
monitorexit,计数器减为 0,释放锁。 - 线程 B 被唤醒,重新竞争 Monitor。
此外,Java 6 以后对 synchronized 做了大量优化,引入了偏向锁 → 轻量级锁 → 重量级锁的升级过程(锁只能升级,不能降级),大幅减少了锁的性能开销。
四、volatile 关键字
1. 可见性
当一个变量被 volatile 修饰时,一个线程对它的修改会立即刷新到主内存,其他线程读取时也会从主内存重新读取,从而保证了多线程间的可见性。
public class VolatileVisibilityDemo {
// 不加 volatile,子线程可能永远看不到 flag 的变化
// 加 volatile 保证修改对其他线程立即可见
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("子线程等待 flag...");
while (!flag) {
// 不加 volatile,这里可能一直循环
}
System.out.println("子线程检测到 flag 变化,退出");
}).start();
Thread.sleep(1000);
flag = true; // 主线程修改 flag
System.out.println("主线程已将 flag 设为 true");
}
}2. 禁止指令重排
编译器和 CPU 为了优化性能,可能会对指令进行重排序。volatile 通过内存屏障来禁止相关指令的重排序。
public class Singleton {
private static volatile Singleton instance; // volatile 禁止重排序
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 问题在这里!
}
}
}
return instance;
}
}为什么需要 volatile?instance = new Singleton() 在底层实际包含三步操作:
- 分配内存空间
- 初始化对象
- 将引用指向分配的内存地址
如果不加 volatile,步骤 2 和 3 可能被重排序,导致其他线程拿到一个未初始化完成的对象(半初始化状态)。volatile 通过内存屏障禁止这种重排序。
3. 不保证原子性
volatile 只保证可见性和有序性,不保证原子性。对于 count++ 这种复合操作,仍会出现线程安全问题。
public class VolatileAtomicDemo {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
// 10 个线程,每个加 1000 次
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 不是原子操作!
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终结果: " + count);
// 期望 10000,实际通常小于 10000
}
}说明:count++ 底层是"读取 → 修改 → 写入"三步操作,volatile 无法保证这三步的原子性。要保证原子性,需使用 synchronized 或 AtomicInteger。
| 特性 | volatile | synchronized |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 原子性 | ❌ | ✅ |
| 有序性 | ✅(禁止指令重排) | ✅(保证串行执行) |
| 是否阻塞 | ❌(不阻塞) | ✅(阻塞) |
五、wait / notify / notifyAll
核心规则
wait()、notify()、notifyAll()都是Object类的方法。- 必须在
synchronized代码块或方法中调用,否则抛IllegalMonitorStateException。 wait()会释放当前持有的锁,并进入WAITING状态。notify()随机唤醒一个等待线程(进入BLOCKED状态等待锁)。notifyAll()唤醒所有等待线程。
示例:生产者-消费者
import java.util.LinkedList;
import java.util.Queue;
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 msg) throws InterruptedException {
while (queue.size() == maxSize) {
System.out.println("队列已满," + Thread.currentThread().getName() + " 等待...");
wait(); // 释放锁,等待消费者消费
}
queue.add(msg);
System.out.println(Thread.currentThread().getName() + " 生产: " + msg);
notifyAll(); // 唤醒可能等待的消费者
}
// 消费者 - 从队列取消息
public synchronized String take() throws InterruptedException {
while (queue.isEmpty()) {
System.out.println("队列为空," + Thread.currentThread().getName() + " 等待...");
wait(); // 释放锁,等待生产者生产
}
String msg = queue.poll();
System.out.println(Thread.currentThread().getName() + " 消费: " + msg);
notifyAll(); // 唤醒可能等待的生产者
return msg;
}
}
public class WaitNotifyDemo {
public static void main(String[] args) {
MessageQueue mq = new MessageQueue(2);
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
mq.put("消息-" + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
// 消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
mq.take();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
producer.start();
consumer.start();
}
}重要:wait() 必须在循环中检查条件(while 而非 if),防止虚假唤醒(notifyAll 唤醒了不该唤醒的线程,或操作系统层面的伪唤醒)。
六、Lock 接口
java.util.concurrent.locks.Lock 接口提供了比 synchronized 更灵活的锁操作。
1. ReentrantLock
ReentrantLock 是 Lock 接口最常用的实现,意为"可重入锁"。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private static int count = 0;
private static final ReentrantLock lock = new ReentrantLock();
public static void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 务必在 finally 中释放锁
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("结果: " + count); // 10000
}
}2. 可重入性
可重入指同一个线程可以多次获取同一把锁而不会死锁。
public class ReentrantDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void outer() {
lock.lock();
try {
System.out.println("outer 获取锁");
inner(); // 再次获取同一把锁
} finally {
lock.unlock();
}
}
public static void inner() {
lock.lock();
try {
System.out.println("inner 获取锁(可重入)");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
outer();
}
}synchronized 也是可重入的。可重入锁内部维护了一个计数器,同一个线程每次获取锁计数器 +1,每次释放 -1,减到 0 时锁才真正释放。
3. 公平锁
ReentrantLock 支持公平模式和非公平模式:
// 公平锁:按线程请求锁的顺序获取(先来先服务)
ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁:允许插队(默认),性能更好
ReentrantLock unfairLock = new ReentrantLock(false); // 默认值ReentrantLock() 无参构造默认创建非公平锁。公平锁性能低于非公平锁,但避免了线程饥饿。
4. Condition 实现精确唤醒
Condition 类似于 wait/notify 的增强版,一个 Lock 可以创建多个 Condition 对象,实现更精细的线程间通信。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
private final String[] items = new String[10];
private int putIndex, takeIndex, count;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 不满条件
private final Condition notEmpty = lock.newCondition(); // 不空条件
public void put(String item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 队列满时,等待"不满"条件
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
count++;
System.out.println(Thread.currentThread().getName() + " 生产: " + item);
notEmpty.signal(); // 唤醒等待"不空"条件的线程
} finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 队列空时,等待"不空"条件
}
String item = items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
System.out.println(Thread.currentThread().getName() + " 消费: " + item);
notFull.signal(); // 唤醒等待"不满"条件的线程
return item;
} finally {
lock.unlock();
}
}
}对比 wait/notify:使用多个 Condition 可以实现精确唤醒(如只唤醒生产者或只唤醒消费者),而 notifyAll 会唤醒所有线程,存在无效竞争。
5. tryLock
tryLock() 尝试获取锁,获取不到立即返回 false,不会阻塞。支持设置超时时间。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void doWork() {
try {
// 尝试获取锁,等待 1 秒,获取不到就放弃
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 获取锁成功");
Thread.sleep(2000);
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取锁失败,执行备选逻辑");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 先让线程-1 持有锁
new Thread(() -> {
lock.lock();
try {
System.out.println("线程-1 持有锁 3 秒");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "线程-1").start();
// 线程-2 尝试获取锁,等 1 秒拿不到就走
new Thread(() -> doWork(), "线程-2").start();
}
}Lock 与 synchronized 对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 使用简单 | ✅ 自动获取/释放锁 | ❌ 需手动 lock/unlock |
| 可重入 | ✅ | ✅ |
| 公平锁 | ❌ 非公平 | ✅ 支持公平/非公平 |
| 中断响应 | ❌ | ✅ lockInterruptibly() |
| 超时等待 | ❌ | ✅ tryLock(timeout) |
| 多条件唤醒 | ❌ 一个条件队列 | ✅ 多个 Condition |
| 性能(高竞争) | 经过优化,接近 Lock | 略优 |
七、线程池
1. ThreadPoolExecutor 七大参数
线程池的核心类是 ThreadPoolExecutor,它有七个关键参数:
| 参数 | 类型 | 说明 |
|---|---|---|
corePoolSize | int | 核心线程数(常驻线程数) |
maximumPoolSize | int | 最大线程数(核心 + 非核心) |
keepAliveTime | long | 非核心线程空闲存活时间 |
unit | TimeUnit | 存活时间单位 |
workQueue | BlockingQueue<Runnable> | 任务队列 |
threadFactory | ThreadFactory | 线程工厂(用于创建线程) |
handler | RejectedExecutionHandler | 拒绝策略 |
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize: 核心线程数 2
5, // maximumPoolSize: 最大线程数 5
10, // keepAliveTime: 非核心线程空闲 10 秒后回收
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), // workQueue: 容量为 3 的有界阻塞队列
new CustomThreadFactory(), // threadFactory: 自定义线程工厂
new ThreadPoolExecutor.AbortPolicy() // handler: 拒绝策略
);
// 提交 8 个任务(core=2, queue=3, max=5, 2+3+5=10, 够用)
// 提交 9 个任务则触发拒绝策略
for (int i = 1; i <= 8; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务-" + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown(); // 不再接受新任务,等待已有任务执行完毕
}
static class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("业务线程-" + count.getAndIncrement());
t.setDaemon(false);
System.out.println("创建线程: " + t.getName());
return t;
}
}
}任务执行流程
提交任务 execute()
│
▼
当前线程数 < corePoolSize?
├── 是 → 创建核心线程执行
└── 否 → 尝试放入 workQueue
├── 队列未满 → 放入队列等待
└── 队列已满
├── 当前线程数 < maximumPoolSize → 创建非核心线程执行
└── 当前线程数 ≥ maximumPoolSize → 执行拒绝策略2. 四种拒绝策略
| 拒绝策略 | 说明 |
|---|---|
AbortPolicy | 直接抛出 RejectedExecutionException(默认) |
CallerRunsPolicy | 由提交任务的线程(调用者线程)自己执行该任务 |
DiscardPolicy | 悄悄丢弃任务,不抛异常 |
DiscardOldestPolicy | 丢弃队列中最老的任务,然后重新尝试提交 |
public class RejectPolicyDemo {
public static void main(String[] args) {
// CallerRunsPolicy 示例:任务被拒绝时由 main 线程执行
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy() // 调用者运行
);
for (int i = 1; i <= 5; i++) {
final int id = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务-" + id);
try { Thread.sleep(500); } catch (InterruptedException e) { }
});
}
executor.shutdown();
// 输出中会看到 main 线程也在执行任务
}
}3. Executors 工具类
Executors 提供了几种预设的线程池,但不建议在生产环境中直接使用(尤其是 newCachedThreadPool 和 newScheduledThreadPool),因为可能导致 OOM。
public class ExecutorsDemo {
public static void main(String[] args) {
// 1. 固定线程数
ExecutorService fixedPool = Executors.newFixedThreadPool(3);
// 底层: new ThreadPoolExecutor(n, n, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>())
// 风险: LinkedBlockingQueue 无界,任务积压可能导致 OOM
// 2. 单线程
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 底层: new ThreadPoolExecutor(1, 1, 0L, ...)
// 3. 缓存线程池
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 底层: new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, ...)
// 风险: 最大线程数为 Integer.MAX_VALUE,大量线程导致 OOM
// 4. 定时/周期任务
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
// 风险: 同上,无界
// 使用示例
fixedPool.execute(() -> System.out.println("任务执行"));
// 定时任务:延迟 1 秒后执行,然后每 2 秒执行一次
scheduledPool.scheduleAtFixedRate(
() -> System.out.println("定时任务: " + System.currentTimeMillis()),
1, 2, TimeUnit.SECONDS
);
// 需要关闭线程池
fixedPool.shutdown();
// scheduledPool.shutdown(); // 通常不关闭,一直运行
}
}生产环境建议:直接使用 new ThreadPoolExecutor(...) 手动指定参数,避免使用 Executors 的默认实现。
八、CountDownLatch / CyclicBarrier / Semaphore
1. CountDownLatch(倒计时门闩)
CountDownLatch 允许一个或多个线程等待其他线程完成操作。计数器减到 0 后不可重置。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int workerCount = 5;
CountDownLatch latch = new CountDownLatch(workerCount);
for (int i = 1; i <= workerCount; i++) {
final int id = i;
new Thread(() -> {
try {
System.out.println("工人-" + id + " 开始工作");
Thread.sleep((long) (Math.random() * 2000));
System.out.println("工人-" + id + " 工作完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数器 -1
}
}).start();
}
System.out.println("主线程等待所有工人完成...");
latch.await(); // 主线程阻塞,直到计数器归零
System.out.println("所有工人完成,主线程继续");
}
}典型场景:并行计算后汇总结果、多服务启动后统一对外提供服务。
2. CyclicBarrier(循环屏障)
CyclicBarrier 让一组线程到达一个屏障点后互相等待,当所有线程都到达后,再一起继续执行。计数器可以循环使用。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int playerCount = 4;
CyclicBarrier barrier = new CyclicBarrier(playerCount, () -> {
System.out.println("=== 所有玩家已就绪,游戏开始 ===");
});
for (int i = 1; i <= playerCount; i++) {
final int id = i;
new Thread(() -> {
try {
System.out.println("玩家-" + id + " 加载资源中...");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("玩家-" + id + " 已就绪");
barrier.await(); // 等待其他玩家
// 所有玩家就绪后,一起开始游戏
System.out.println("玩家-" + id + " 进入游戏");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}CountDownLatch vs CyclicBarrier:
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 计数器能否重置 | ❌ 一次性 | ✅ 可循环使用 |
| 等待对象 | 一个/多个线程等待其他线程 | 所有线程互相等待 |
| 构造函数 | new CountDownLatch(n) | new CyclicBarrier(n, barrierAction) |
| 计数方式 | countDown() 递减 | await() 递增 |
3. Semaphore(信号量)
Semaphore 用于控制同时访问某个资源的线程数量,可以看作是一种共享锁。
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
// 最多允许 3 个线程同时访问
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 10; i++) {
final int id = i;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println("线程-" + id + " 进入停车场(剩余: " + semaphore.availablePermits() + ")");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("线程-" + id + " 离开停车场");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}, "线程-" + i).start();
}
}
}典型场景:数据库连接池限流、停车场车位管理、接口限流。Semaphore(permits, fair) 也支持公平模式。
小结
本文介绍了 Java 多线程编程的核心基础知识点:
| 知识点 | 核心要点 |
|---|---|
| 线程创建 | Thread、Runnable(无返回值)、Callable+F处处FutureTask(有返回值) |
| 线程生命周期 | 6 种状态:NEW → RUNNABLE → TERMINATED,中间可进入 BLOCKED/WAITING/TIMED_WAITING |
| synchronized | 对象锁(锁实例)、类锁(锁 Class 对象),底层基于 Monitor 实现 |
| volatile | 可见性 + 禁止指令重排,不保证原子性,适用于状态标志位 / DCL 单例 |
| wait/notify | 线程间通信,必须在 synchronized 块中调用,用 while 防止虚假唤醒 |
| Lock/Condition | ReentrantLock(可重入、公平锁、tryLock),Condition 实现精确唤醒 |
| 线程池 | 7 大参数 + 4 种拒绝策略,核心流程:core → queue → max → reject |
| 并发工具类 | CountDownLatch(一次等待)、CyclicBarrier(循环等待)、Semaphore(限流) |
多线程编程的难点不在于语法,而在于对共享资源竞争和线程协作的理解。遵循"尽量避免共享、必须共享则加锁、加锁范围尽可能小"的原则,可以写出安全高效的并发程序。