前言
JUC 提供了比传统 synchronized 和 wait/notify 更灵活、更高效的并发工具。
一.Callable
对于传统的 Runnable 接口来说,任务执行完毕后,主线程无法直接获取计算结果,且无法抛出受检异常(run()方法签名未声明 throws,导致异常只能内部消化)。
1.Callable的实现
Callable 接口则可以解决 Runnable 带来的问题,Callable 是一个泛型接口,定义如下:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) {
//通过Callable来描述一个任务,泛型参数表示返回值的类型
Callable<Integer> callable = new Callable<Integer>() {
//重写callable中的call方法
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=0;i<=1000;i++){
sum += i;
}
return sum;
}
};
//为了让线程执行 callable 中的任务,需要创建一个辅助的类对Callable进行封装,线程通过该类获取任务
FutureTask<Integer> task = new FutureTask<Integer>(callable);
//创建线程,用来执行任务
Thread t =new Thread(task);
t.start();
//如果线程任务没有执行完成,get就会陷入阻塞
//会一直阻塞到任务完成,得出计算结果
try {
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- call() 方法声明了 throws IOException,因此任务内部可以直接 throw 受检异常,无需 try-catch 包裹。
- 调用 FutureTask.get() 时,若任务抛出异常,get() 方法会将原始异常包装为 ExecutionException 抛出。
2.Future 接口
Future 是一个泛型接口,代表异步计算的结果
主线程将任务交给子线程后,可以得到一个Future。主线程可以先做别的事,需要时凭借Future的 get() 即可拿到结果,若线程还没完成则原地等待。
| 方法 | 描述 |
|---|---|
| boolean cancel(boolean mayInterruptIfRunning) | 尝试取消任务执行。 |
| boolean isCancelled() | 判断任务是否已被取消。 |
| boolean isDone() | 判断任务是否执行完成(正常结束、异常结束或取消均返回 true)。 |
| V get() | 阻塞获取计算结果,直到任务执行完毕。 |
| V get(long timeout, TimeUnit unit) | 带超时时间的阻塞获取,超时抛出 TimeoutException。 |
3.FutureTask
FutureTask 是 Future 接口的唯一实现类,同时它也实现了 RunnableFuture 接口(该接口继承自 Runnable 和 Future)。
- Runnable 身份:作为 Thread 构造函数的参数,负责执行 Callable 中的任务逻辑。
- Future 身份:作为调用方获取结果的句柄,提供 get()、cancel() 等方法。
FutureTask 是连接 Callable 任务与 Thread 线程的桥梁,同时承担了存储异步计算结果与异常的重任。
二.ReentrantLock
ReentrantLock 是 Java 并发包中提供的一把显式锁,需要开发者手动控制加锁与解锁
1.ReentrantLock的使用
- lock.lock():解释
- lock.unlock():解释
ReentrantLock lock = new ReentrantLock();
// 加锁
lock.lock();
try {
// 受保护的临界区代码
// 例如:对共享变量进行写操作
} finally {
// 务必在 finally 块中释放锁,避免因异常导致死锁
lock.unlock();
}
2.ReentrantLock 与 Synchronized 对比
| 对比维度 | synchronized 关键字 | ReentrantLock 类 |
|---|---|---|
| 实现层面 | JVM 内置关键字,由 C++ 实现 | JDK 层面的 Java 类,基于 AQS 框架实现 |
| 锁的释放 | 自动释放:代码块执行完毕或抛出异常时,JVM 自动释放锁 | 手动释放:必须在 finally 中显式调用 unlock() |
| 公平策略 | 仅支持非公平锁 | 构造函数支持选择两种锁类型,new ReentrantLock(true) 可创建公平锁 |
| 等待可中断 | 线程在等待锁期间只能被动死等,无法被外部打断。一旦陷入阻塞,除非成功获取锁,否则线程将一直处于 BLOCKED 状态,无法响应中断信号 | 提供了 lockInterruptibly() 方法,支持响应中断。当线程 A 在等待锁时,若其他线程调用了 A.interrupt(),线程 A 会立刻从阻塞中醒来,并抛出 InterruptedException,从而让上层代码有机会处理取消逻辑 |
| 尝试获取锁 | 一旦锁被占用,当前线程别只能进入阻塞队列无限期等待,直到锁被释放 | tryLock():一次性的尝试。若锁空闲则立即获取并返回 true;若锁被占用则立即返回 false,线程不会阻塞,可转而执行其他备选逻辑 tryLock(long time, TimeUnit unit):带超时的尝试。在指定时间内反复尝试获取锁,超时未获取则自动放弃并返回 false |
| 条件队列 | 单一 wait/notify/notifyAll 机制 | 支持多个 Condition 条件队列,可精确唤醒特定类型的等待线程 |
| 性能表现 | JDK 1.6 优化后,基础场景下两者性能相近 | 在复杂同步策略或高竞争场景下功能更灵活 |
4.Condition
Condition是 ReentrantLock 实现条件等待/唤醒机制的核心组件,与 synchronized 的 wait/notify 相对应。
可以通过 ReentrantLock.newCondition() 创建
- Condition.await():await():让当前线程进入条件等待队列,并释放锁。
- Condition.signal():唤醒一个在该 Condition 上等待的线程。
- Condition.signalAll():唤醒所有在该 Condition 上等待的线程。
代码例子
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
// 两个不同的条件队列:一个管“不满”,一个管“不空”
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final int[] buffer = new int[10];
private int count = 0; // 当前队列元素个数
// 生产者方法
public void produce(int value) throws InterruptedException {
lock.lock();
try {
// 队列满了,生产者进入 notFull 条件队列等待
while (count == buffer.length) {
System.out.println("队列已满,生产者等待...");
notFull.await();
}
// 生产数据
buffer[count] = value;
count++;
System.out.println("生产: " + value + ",当前数量: " + count);
// 唤醒一个在 notEmpty 条件队列上等待的消费者
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 消费者方法
public int consume() throws InterruptedException {
lock.lock();
try {
// 队列空了,消费者进入 notEmpty 条件队列等待
while (count == 0) {
System.out.println("队列已空,消费者等待...");
notEmpty.await();
}
// 消费数据
int value = buffer[count - 1];
count--;
System.out.println("消费: " + value + ",当前数量: " + count);
// 唤醒一个在 notFull 条件队列上等待的生产者
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo demo = new ConditionDemo();
// 启动一个生产者线程
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
demo.produce(i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 启动一个消费者线程
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
demo.consume();
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
存在两个等待队列,一个放生产者,一个放消费者。

3.AQS抽象类
AQS(AbstractQueuedSynchronizer)是Java中的一个抽象类。 AQS是一个用于构建锁、同步器、协作工具类的工具类,是 JUC 包的基石,是一个用于构建锁、同步器和协作工具类的抽象框架。
ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等并发工具的内部同步器均基于 AQS 实现。
AQS 通过两大核心组件来管理线程的同步状态:
(1)同步状态 —— volatile int state(被 volatile 修饰的整型变量,用来标识资源的状态)
- 作用:表示锁的持有情况。
- 操作:通过 getState()、setState() 以及 CAS 来安全修改状态值。
- 语义映射(以 ReentrantLock 为例):
- state == 0:锁空闲。
- state >= 1:锁已被线程持有。若 state > 1,表示该线程发生了重入。
(2)等待队列 —— CLH 变体队列
- 数据结构:一个虚拟的 FIFO 双向链表。
- 节点类型:内部类 Node,封装了等待线程的引用、等待状态(waitStatus)以及前后指针。
- 工作机制:
- 入队:当线程尝试获取锁失败时,会被包装成 Node 节点,通过 CAS 操作原子性地加入到队列尾部,随后通过 LockSupport.park() 挂起。
- 出队:当锁被释放时(state 置 0),AQS 会唤醒队列的头节点后继(通常是第一个有效等待节点),被唤醒的节点再次尝试 CAS 获取锁。
4.ReentrantLock 基于 AQS 的实现

synchronized加锁是自适应:无竞争 → 偏向锁 → 轻量级锁(自旋) → 重量级锁(阻塞)
三.原子类 (Atomic 类)
原子类的内部基于CAS机制实现,性能比加锁实现 i++ 高很多,常用的原子类有如下几个:
| 类名 | 说明 |
|---|---|
| AtomicBoolean | 布尔型原子类 |
| AtomicInteger | 整型原子类 |
| AtomicLong | 长整型原子类 |
| AtomicIntegerArray | 长整型原子类 |
| AtomicLong | 整型数组原子类 |
| AtomicReference | 引用类型原子类 |
| AtomicStampedReference | 原子更新带有版本号的引用类型 |
以AtomicInteger举例,常见的方法有
addAndGet(int delta); i += delta
decrementAndGet(); --i
getAndDecrement(); i--
incrementAndGet(); ++i
getAndIncrement(); i++
四.线程池
1.线程池的创建
(1)ExecutorService 和 Executors
ExecutorService 表示一个线程实例,Executors是一个工厂类,能给个创建出几种不同风格的线程池。通过ExecutorService 中的 submit 方法能够向线程池中提交若干个任务
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors创建线程的几种方式
| 方法 | 说明 |
|---|---|
| newFixedThreadPool | 创建固定线程数的线程池 |
| newCachedThreadPool | 创建线程数目动态增长的线程池 |
| newSingleThreadExecutor | 创建只包含单个线程的线程池 |
| newScheduledThreadPool | 设定延迟时间后执行命令,或定期执行命令,是进阶版的Timer |
(2)ThreadPoolExecutor
Executors本质上是对ThreadPoolExecutor的封装,ThreadPoolExecutor提供了更多的可选参数,库进一步细化线程池行为的设定,其构造方法如下。
手动创建 ThreadPoolExecutor 需要指定 7 个核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- int corePoolSize 表示
核心线程数 - int maximumPoolSize 表示
最大线程数,包括核心线程和非核心线程。 - long keepAliveTime 表述了非核心线程的存活时间
- TimeUnit unit 表示keepAliveTime的时间单位
- BlockingQueue workQueue 任务队列,线程池会提供一个submit方法将任务放入到线程池任务队列中
- ThreadFactory threadFactory 线程工厂,描述线程是怎么创建出来的
- RejectedExecutionHandler handler,拒绝策略,当任务队列满了设定的 具体策略,包括忽略最新任务、阻塞等待、丢弃最旧任务等方式
(3)四种常见拒绝策略
| 策略类 | 行为描述 |
|---|---|
| AbortPolicy (默认) | 直接抛出 RejectedExecutionException 异常,阻断系统运行。 |
| CallerRunsPolicy | 由调用者线程(提交任务的线程)执行该任务,起到流量削峰作用。 |
| DiscardPolicy | 直接丢弃新任务,无任何通知(极度危险,慎用)。 |
| DiscardOldestPolicy | 丢弃队列中存活最久的任务,然后尝试重新提交新任务。 |
2.线程池的原理与使用
线程池包含核心线程数、最大线程数以及一个任务等待队列。
- 核心线程数 (corePoolSize):除非系统空闲回收,否则始终驻留的线程,用于快速响应常态任务量。
- 最大线程数 (maximumPoolSize):线程池扩容的极限值,用于应对突发峰值流量。
- 任务等待队列 (workQueue):起到缓冲池的作用,当核心线程繁忙时,任务先进入队列排队等待。

下面的代码基于 ThreadPoolExecutor 手动创建
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize 核心线程数
5, // maximumPoolSize 最大线程数
60, // keepAliveTime 空闲存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 有界任务队列,容量为 3
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行
);
// 2. 提交任务
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务: " + taskId);
try {
Thread.sleep(2000); // 模拟业务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3. 关闭线程池(不再接收新任务,等待已提交任务执行完毕)
executor.shutdown();
}
}
3.线程池的参数设置
1)CPU 密集型任务
- 主要消耗 CPU 资源进行计算,几乎无阻塞。
- 核心线程数 = CPU 核心数 + 1。
- 防止过多线程导致频繁上下文切换,加 1 是为了利用因缺页中断或其他原因暂停的空隙。
(2)IO 密集型任务
- 频繁进行网络或磁盘读写,线程大部分时间处于阻塞等待状态。
- 核心线程数 = CPU 核心数 * 2 或使用公式 CPU 核心数 / (1 - 阻塞系数)。
- 阻塞期间线程不占用 CPU,可多开线程利用 CPU 空闲时间处理其他任务。
(3)通用估算公式
- 最佳线程数 = CPU 核心数 × 目标 CPU 利用率 × (1 + 平均等待时间 / 平均计算时间)
五、同步工具类
1.CountDownLatch
CountDownLatch 允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
| 方法 | 描述 |
|---|---|
| CountDownLatch(int count) | 构造函数,设定需要等待的事件数量。 |
| void await() | 阻塞当前线程,直到计数器的值变为 0。 |
| boolean await(long timeout, TimeUnit unit) | 带超时的阻塞等待,超时返回 false。 |
| void countDown() | 计数器减 1。当计数归零时,释放所有等待线程。 |
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int taskNum = 5;
CountDownLatch latch = new CountDownLatch(taskNum);
for (int i = 0; i < taskNum; i++) {
int finalI = i;
new Thread(() -> {
try {
System.out.println("子任务 " + finalI + " 开始执行...");
Thread.sleep((long) (Math.random() * 2000));
System.out.println("子任务 " + finalI + " 执行完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 无论是否异常,确保计数器递减
}
}).start();
}
System.out.println("主线程等待所有子任务完成...");
latch.await(); // 阻塞直到 latch.getCount() == 0
System.out.println("所有子任务已完成,主线程继续执行!");
}
}
计数器的值在构造时设定,一旦归零就无法重置。如果需要重复使用,应改用 CyclicBarrier
2.CyclicBarrier
CyclicBarrier 让一组线程互相等待,直到所有线程都到达一个公共的屏障点(Barrier Point),然后所有线程才继续执行。
| 方法 | 描述 |
|---|---|
| CyclicBarrier(int parties) | 构造函数,设定需要拦截的线程数量。 |
| CyclicBarrier(int parties, Runnable barrierAction) | 当所有线程到达屏障后,优先执行 barrierAction。 |
| int await() | 阻塞当前线程,等待其他线程到达屏障。返回值为当前线程的到达索引。 |
| void reset() | 将屏障重置为初始状态(若有等待线程会抛出 BrokenBarrierException)。 |
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int playerNum = 3;
CyclicBarrier barrier = new CyclicBarrier(playerNum, () -> {
// 这个 Runnable 由最后一个到达屏障的线程执行
System.out.println("*** 人齐了,开始匹配游戏!***");
});
for (int i = 1; i <= playerNum; i++) {
int playerId = i;
new Thread(() -> {
try {
System.out.println("玩家 " + playerId + " 已上线,等待队友...");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("玩家 " + playerId + " 已到达大厅,等待其他人...");
barrier.await(); // 等待其他人
System.out.println("玩家 " + playerId + " 进入游戏房间!");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
计数器归零后会自动重置,可以用于新一轮的等待
支持在所有线程到达后执行一个前置任务(barrierAction)
3.Semaphore
Semaphore 用于控制同时访问特定资源的线程数量(即限流)
| 方法 | 描述 |
|---|---|
| Semaphore(int permits) | 构造函数,设定许可证(permits)数量。 |
| Semaphore(int permits, boolean fair) | 可设置是否使用公平队列(默认非公平)。 |
| void acquire() | 获取一个许可,若无可用许可则阻塞等待。 |
| void release() | 释放一个许可,将其返还给信号量。 |
| boolean tryAcquire() | 尝试非阻塞获取许可,立即返回结果。 |
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
int parkingSlots = 3; // 只有 3 个停车位
Semaphore semaphore = new Semaphore(parkingSlots);
for (int i = 1; i <= 6; i++) { // 来了 6 辆车
int carId = i;
new Thread(() -> {
try {
System.out.println("汽车 " + carId + " 到达停车场入口,等待车位...");
semaphore.acquire(); // 申请车位
System.out.println("汽车 " + carId + " 成功进入停车场,停车中...");
Thread.sleep((long) (Math.random() * 5000));
System.out.println("汽车 " + carId + " 驶离停车场。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 务必释放车位
}
}).start();
}
}
}
常用于数据库连接池、API 限流等场景。
可通过 release(permits) 动态增加许可数量(慎用,通常用于资源扩容)
六、线程安全的集合类
在多线程环境下,直接使用 ArrayList、HashMap 等非线程安全的集合类会导致数据错乱甚至死循环。Java 标准库及 JUC 包提供了多种线程安全集合方案,下面分别从 List、Queue、Map 三类进行梳理。
1.多线程环境使用List
1.在访问 ArrayList 的代码块外手动添加 synchronized 或 ReentrantLock
2.使用同步包装器 Collections.synchronizedList():
- Collections.synchronizedList(new ArrayList<>())是标准库中提供的一个基于synchronized进行线程同步的List,synchronizedList 的关键操作上都带有synchronized。
3.使用CopyOnWriteArrayList:
- 写时拷贝,在修改的时候会创建出副本,对于一个ArrayList{1,2,3,4},如果是多线程读,则不会产生线程安全问题,如果多线程写,则会把ArrayList给复制一份,优先修改副本,这样在修改的同时,对读操作是没有任何影响的,即读的时候先读旧的版本,避免了读到未修改完成的中间状态
2.多线程环境使用Queue
JUC 提供了丰富的 BlockingQueue 实现,用于解决生产者-消费者场景下的线程安全问题。
| 队列 | 说明 |
|---|---|
| ArrayBlockingQueue | 基于数组实现的阻塞队列 |
| LinkedBlockingQueue | 基于链表实现的阻塞队列 |
| PriorityBlockingQueue | 基于堆实现的阻塞队列 |
| TransferQueue | 只包含一个元素的阻塞队列 |
多线程环境使用Map
1.HashMap本身是线程不安全的,而HashTable 通过给方法直接加锁来保证线程的安全,针对this来进行加锁(针对对象进行加锁),因此当有多个线程访问这个Hashtable的时候,会导致锁竞争的概率非常大,效率较低。
public synchronized V get(Object key)
public synchronized V put(K key, V value)
...
2.ConcurrentHashMap 操作元素的时候,是针对这个元素所在的链表头结点来进行加锁的(锁桶),如果两个线程针对两个不同链表上的元素进行操作,没有线程安全问题不必要加锁。由于Hash表中,链表的数目非常多,每个链表的长度是相对短的,因此可以保证冲突的概率非常小了
3.ConcurrentHashMap的优点
- ConcurrentHashMap减少了锁冲突,即锁加在每个链表的头结点上。
- ConcurrentHashMap只是针对写操作加锁了,读操作没有加锁,只是使用volatie。
- ConcurrentHashMap更广泛的使用了CAS,进一步提高了效率。
- .ConcurrentHashMap针对扩容进行了巧妙的化整为零。如果链表过长,就会影响Hash表的效率,扩容就需要创建一个更大的数组,把之前的元素给搬运过去,效率低,对于HashTable来说,只要这次put触发了扩容就一口气搬完,导致这次put非常卡顿。而ConcurrentHashMap每次操作只搬运一点点,通过多从操作完成整个搬运过程,同时维护一个新的 HashMap 和一个旧的 HashMap ,查找的时候既要查找旧的也查找新的,插入的时候只插入新的,直到搬运完毕才销毁旧的
七.面试问题
1.线程获取不到锁会直接进入到阻塞队列吗?
- synchronized:会先进行自旋等待(默认循环 10 次),自旋期间不进入阻塞队列,如果自旋期间拿到了锁,就继续执行;如果自旋失败或竞争加剧,才会膨胀为重量级锁,此时才会调用操作系统 mutex,线程真正进入阻塞队列(等待队列),状态变为 BLOCKED。
- ReentrantLock:默认非公平锁的 lock() 方法:先尝试一次 CAS 抢占(插队),失败后判断是否可重入。
- 若无法重入,则立即将当前线程包装成 Node 节点,通过 CAS 加入 AQS 的 CLH 等待队列尾部,然后调用 LockSupport.park() 挂起。
- 线程状态为 WAITING,而非 BLOCKED。
- 若使用 tryLock(),则完全不入队,拿不到锁直接返回 false。
2.CAS 和 AQS 有什么关系?
- CAS(Compare And Swap)是一种无锁原子操作,通过 CPU 指令保证比较与交换的原子性。
- AQS(AbstractQueuedSynchronizer)是 JUC 包的基石框架,用于构建锁和同步器。
- CAS 是 AQS 实现线程安全状态切换的核心工具。
- AQS 通过 compareAndSetState()(CAS)来安全地修改 volatile int state(锁状态)。
- 线程入队、出队时,也通过 CAS 保证 CLH 队列的并发安全。
- CAS 是 AQS 的底层,AQS 是 CAS 在同步框架中的上层封装与应用
3.Condition 和 Object 监视器方法有什么区别
| Object 监视器方法 | Condition | |
|---|---|---|
| 绑定锁类型 | 只能与 synchronized 配合使用 | 必须与 ReentrantLock 配合使用 |
| 等待队列数量 | 每个锁对象只有 1 个隐式等待队列 | 每个锁可创建多个显式条件队列 |
| 唤醒粒度 | notify() 随机唤醒一个;notifyAll() 唤醒全部 | signal() 精准唤醒指定 Condition 上的线程 |
| 中断响应 | wait() 响应中断 | await() 响应中断;另有 awaitUninterruptibly() 不响应中断 |
| 超时等待 | wait(long timeout) | await(long time, TimeUnit unit) 以及 awaitNanos/Until 等丰富方法 |
4.为什么说 Condition 比 wait/notify 更高效?
- 复杂同步场景(如生产者-消费者)中,wait/notify 只有一个等待队列,当队列满时生产者阻塞,队列空时消费者阻塞,但两者都会进入同一个等待集合。调用 notifyAll() 会同时唤醒生产者和消费者,导致大量线程被唤醒后因条件不满足而再次休眠,
- 而 Condition 允许将不同原因的等待线程放入不同队列,当消费者消费数据后,只需要调用 notFull.signal() 唤醒一个生产者,无需打扰其他消费者。
- 生产者因“队列满”阻塞,进入 notFull 队列。
- 消费者因“队列空”阻塞,进入 notEmpty 队列。
5.为什么要使用线程池,线程池的作用
- 降低资源消耗:复用已创建的线程,避免频繁创建/销毁线程带来的 CPU 和内存开销。
- 提高响应速度:任务到达时,无需等待线程创建过程,可立即由空闲线程执行。
- 提高线程的可管理性:可统一分配、监控、调优线程数量,防止无限制创建线程导致系统资源耗尽
6.核心线程数设置为0可不可以?
-
可以,corePoolSize = 0 表示线程池不保留任何核心线程,到达的任务被扔进等待队列,所有线程在空闲超过 keepAliveTime 后都会被回收。
-
适用场景:
- 配合 SynchronousQueue:每来一个任务必须立即有一个线程处理,否则任务被拒绝或等待。CachedThreadPool 正是这种配置(核心 0,最大 Integer.MAX_VALUE)。
- 配合 有界队列:当任务量极低时,可让线程数降为 0,节省资源;任务到达后再创建新线程。
-
注意事项:若队列不为空且核心线程为 0,任务提交后可能需等待新线程创建,有一定延迟。
7.线程池用了哪些设计模式?
- 工厂模式:ThreadFactory 接口用于定制线程的创建逻辑(设置名称、优先级、守护状态等)。ThreadPoolExecutor 构造参数里可以传入一个 ThreadFactory。它的作用是:每需要创建一个新线程时,都调用这个工厂来生产
- 生产者-消费者模式:调用 submit() 的线程是生产者,将任务放入队列;线程池中的工作线程是消费者,从队列中取出任务执行。
- 策略模式:RejectedExecutionHandler 接口定义了四种拒绝策略(AbortPolicy、CallerRunsPolicy 等),可灵活替换。
- 模板方法模式:ThreadPoolExecutor 的 beforeExecute() 和 afterExecute() 方法留给子类扩展,实现任务执行前后的钩子逻辑。
8.shutdown和shutdownNow的区别
- shutdown将线程池状态设置为 SHUTDOWN,shutdownNow设置为 STOP;
- shutdown仅中断空闲线程,shutdownNow中断所有工作线程(包括正在执行任务的线程);
- shutdown对于已提交但未执行的任务继续等待执行,shutdownNow则清空队列,并返回未执行的任务列表;
- shutdown对于已开始执行的任务继续执行至结束,shutdownNow已开始执行的任务被中断。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
9.提交给线程池中的任务可以被撤回吗?
- 可以,当向线程池提交任务时,会得到一个Future对象。这个Future对象提供了几种方法来管理任务的执行,包括取消任务。
- 通过 Future.cancel(),若任务尚未开始执行则标记取消,后续不再执行;若任务正在执行中,那么cancel(true)设置任务必须响应中断,cancel(false)不会中断线程;若任务已完成,则取消失败,返回 false。
Future<?> future = executor.submit(runnable);
future.cancel(true); // 尝试取消任务
- 通过 remove() 从队列中移除(仅限未执行任务)
BlockingQueue<Runnable> queue = threadPool.getQueue();
queue.remove(runnable); // 从等待队列中移除指定任务


2367

被折叠的 条评论
为什么被折叠?



