文章目录
- 前言
- 一.Callable
-
- 1.Callable的实现
- [2.Future 接口](#2.Future 接口)
- 3.FutureTask
- 二.ReentrantLock
-
- 1.ReentrantLock的使用
- [2.ReentrantLock 与 Synchronized 对比](#2.ReentrantLock 与 Synchronized 对比)
- 4.Condition(非juc包)
- 3.AQS抽象类
- [4.ReentrantLock 基于 AQS 的实现](#4.ReentrantLock 基于 AQS 的实现)
- 三.原子类 (Atomic 类)
- 四.线程池
-
- 1.线程池的创建
-
- [(1)ExecutorService 和 Executors](#(1)ExecutorService 和 Executors)
- (2)ThreadPoolExecutor
- (3)四种常见拒绝策略
- 2.线程池的原理与使用
- 3.线程池的参数设置
- 五、同步工具类
- 六、线程安全的集合类
- 七.面试问题
前言
JUC 提供了比传统 synchronized 和 wait/notify 更灵活、更高效的并发工具。
一.Callable
对于传统的 Runnable 接口来说,任务执行完毕后,主线程无法直接获取计算结果,且无法抛出受检异常(run()方法签名未声明 throws,导致异常只能内部消化)。
1.Callable的实现
Callable 接口则可以解决 Runnable 带来的问题,Callable 是一个泛型接口,定义如下:
java
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
java
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():解释
java
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(非juc包)
Condition 不是juc包下的,而是ReentrantLock 的衍生?
Condition.await():
Condition.signal():
代码例子
java
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 的实现
【补图】
三.原子类 (Atomic 类)
原子类的内部基于CAS机制实现,性能比加锁实现 i++ 高很多,常用的原子类有如下几个:
| 类名 | 说明 |
|---|---|
| AtomicBoolean | 布尔型原子类 |
| AtomicInteger | 整型原子类 |
| AtomicLong | 长整型原子类 |
| AtomicIntegerArray | 长整型原子类 |
| AtomicLong | 整型数组原子类 |
| AtomicReference | 引用类型原子类 |
| AtomicStampedReference | 原子更新带有版本号的引用类型 |
以AtomicInteger举例,常见的方法有
java
addAndGet(int delta); i += delta
decrementAndGet(); --i
getAndDecrement(); i--
incrementAndGet(); ++i
getAndIncrement(); i++
四.线程池
1.线程池的创建
(1)ExecutorService 和 Executors
ExecutorService 表示一个线程实例,Executors是一个工厂类,能给个创建出几种不同风格的线程池。通过ExecutorService 中的 submit 方法能够向线程池中提交若干个任务
java
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 个核心参数
java
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 手动创建
java
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
2.CyclicBarrier
3.Semaphore
六、线程安全的集合类
七.面试问题
1.线程获取不到锁会直接进入到阻塞队列吗?
- synchronized:会先进行自旋等待(默认循环 10 次),自旋期间不进入阻塞队列,如果自旋期间拿到了锁,就继续执行;如果自旋失败或竞争加剧,才会膨胀为重量级锁,此时才会调用操作系统 mutex,线程真正进入阻塞队列(等待队列),状态变为 BLOCKED。
- ReentrantLock:
2.CAS 和 AQS 有什么关系?
1.为什么要使用线程池,线程池的作用
2.核心线程数设置为0可不可以?
3.线程池用了哪些设计模式?
4.shutdown和shutdownNow的区别
java
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
java
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;
}
5.提交给线程池中的任务可以被撤回吗?
