多线程面试
一. 线程创建的四种方式?
通过实现Thread类和实现RUNNABLE和Callable 接口 + FutureTask
高并发场景:使用线程池
Callable接口使用:
1. 用线程池 + submit + Callable + Future
2. Thread + Callable + FutureTask
RUNNABLE接口的使用:
Thread+Runnable
二. future和futuretask的区别?
future是一个接口,只用来获取异步的结果。futureTask是一个类,它实现了runnable和future接口。既可以作为任务给线程池进行处理,也可以通过get方法来获取线程执行结果的返回值。
三. yeild,join,wait,sleep区别?
| 方法 | 所属类 | 是否释放锁 | 用途 | 使用场景 |
|---|---|---|---|---|
yield() |
Thread 类 |
❌ 不释放锁 | 暂时让出 CPU,让其他同优先级线程有机会执行,不阻塞 | 提高线程调度灵活性(较少使用) |
join() |
Thread 类 |
❌ 不释放锁 | 当前线程等待另一个线程执行完毕 | 线程串行执行(A 等 B 执行完) |
wait() |
Object 类 |
✅ 释放锁 | 当前线程等待,被唤醒后重新竞争锁(需配合 notify/notifyAll) |
多线程协作(生产者-消费者模型) |
sleep() |
Thread 类 |
❌ 不释放锁 | 当前线程休眠指定时间,到时继续执行 | 定时、模拟延迟、轮询场景等 |
四. 线程的几种状态?
新建,就绪,执行,阻塞,终止。
| New(新建) | 调用 new Thread() 创建线程,但未启动 |
| Runnable(就绪) | 调用 start() 后进入就绪队列,等待 CPU 调度 |
| Running(执行) | 获得 CPU 时间片,开始执行 run() 方法 |
| Blocked / Waiting(阻塞/等待) | 执行中被挂起,如 sleep()、wait()、等待 IO |
| Terminated(终止) | 线程执行完毕或抛出异常,生命周期结束 |
五. 线程如何进行通信?
三种方式:
1. volatile :通过定义一个全局变量
public class VolatileDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
System.out.println("线程A 等待 flag 变为 false...");
while (flag) {} // 轮询等待
System.out.println("线程A 结束");
}).start();
new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
flag = false;
System.out.println("线程B 修改了 flag 为 false");
}).start();
}
}
2. synchronized和wait/notify/notify all 实现线程的通信
1. 无法精准的唤醒 2. 不能中断 3. 只能等待一个队列
wait/notify/notify all必须放在同步代码块或者同步方法中。
理由:这几个方法基于对象的监视器锁实现的,只有持有对象的监视器的锁,才能调用这些方法。
public class WaitNotifyExample {
private static final Object lock = new Object();
private static int number = 1;
private static final int MAX = 10;
public static void main(String[] args) {
Thread oddThread = new Thread(() -> {
while (number <= MAX) {
synchronized (lock) {
if (number % 2 == 1) {
System.out.println("奇数线程:" + number++);
lock.notify(); // 唤醒偶数线程
} else {
try {
lock.wait(); // 等待偶数线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread evenThread = new Thread(() -> {
while (number <= MAX) {
synchronized (lock) {
if (number % 2 == 0) {
System.out.println("偶数线程:" + number++);
lock.notify(); // 唤醒奇数线程
} else {
try {
lock.wait(); // 等待奇数线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
oddThread.start();
evenThread.start();
}
}
3,ReentrantLock和Condition(await, signal, signal all)
1. 精准唤醒 2. 支持中断 3. 多条件等待 4. 支持公平锁
使用场景: 实际开发中,如果只需要简单互斥和通知,用 synchronized 即可;但如果有多个条 件、需要可中断或更灵活的锁控制,就推荐用 ReentrantLock 和 Condition。
案例1:一个lock和三个condition条件等待
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ABCPrinter {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condA = lock.newCondition();
private final Condition condB = lock.newCondition();
private final Condition condC = lock.newCondition();
private int state = 0; // 0: A, 1: B, 2: C
public void printA() {
lock.lock();
try {
while (state % 3 != 0) {
condA.await();
}
System.out.println("A");
state++;
condB.signal(); // 唤醒打印B的线程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (state % 3 != 1) {
condB.await();
}
System.out.println("B");
state++;
condC.signal(); // 唤醒打印C的线程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (state % 3 != 2) {
condC.await();
}
System.out.println("C");
state++;
condA.signal(); // 唤醒打印A的线程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ABCPrinter printer = new ABCPrinter();
Runnable a = () -> {
for (int i = 0; i < 5; i++) {
printer.printA();
}
};
Runnable b = () -> {
for (int i = 0; i < 5; i++) {
printer.printB();
}
};
Runnable c = () -> {
for (int i = 0; i < 5; i++) {
printer.printC();
}
};
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
-
使用
ReentrantLock进行互斥控制,避免多个线程并发修改state -
每个线程使用不同的
Condition,实现精准唤醒 -
每次执行都判断
state % 3,确保按顺序执行 -
每轮只唤醒下一个目标线程,提升效率,避免
notifyAll的资源竞争问题
六. 线程池的七大核心参数?
总的来说有七大核心参数。
1. 核心线程数 2. 最大线程数 3. 任务队列 4. 线程的工厂 5. 线程存活的时间 6. 线程存活时间的单位 7. 拒绝策略
| 类型 | corePoolSize |
maximumPoolSize |
|---|
| CPU 密集型 | CPU核心数 | CPU核心数 + 1 |
| IO 密集型 | CPU核心数 | 2 × CPU核心数(或更多) |
int cpuCount = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCount, // corePoolSize //cpu的核数
cpuCount * 2, // maximumPoolSize //根据cpu和io密集型判断
60L, TimeUnit.SECONDS, // keepAliveTime + unit
new ArrayBlockingQueue<>(100),// 有界队列防止 OOM
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
线程池哪几种队列:ArrayBlockingQueue有界队列 LinkedBlockingQueue 无界队列
PriorityBlockingQueue 按优先级执行任务,需要任务实现 Comparable
七. 线程池工作的流程?
总的来说,线程池是通过(核心线程数+任务队列+最大线程数+拒绝策略)这套机制,实现任务的 弹性处理和资源高效的利用。避免了线程的频繁创建造成资源的浪费。
具体工作流程:往线程池提交任务的时候,先创建核心线程,随着提交任务的增加,如果核心线程满了,会将提交的任务加到任务队列里面。如果任务队列满了,会创先最大线程来执行任务。如果最大线程数满了,会执行对应的拒绝策略。
当任务执行完以后,非核心线程达到keepalivetime时间以后会被销毁。核心线程可以长期的存活。
八. 线程池的拒绝策略?
默认四种拒绝策略。
1. 直接抛出异常:不允许丢弃任务(默认的拒绝策略)
2. 丢弃任务不抛出异常:运行任务丢失
3. 让提交任务的线程来执行该任务
4. 丢弃掉队列中最早的任务,然后尝试执行新的任务
九. 线程池几种阻塞队列?
一共5种。 有界队列(数组实现),无界队列(链表实现),延迟队列,优先级队列,同步队列。
线程池提交 execute 和 submit 有什么区别?
execute 没有返回值。适应场景:不关心异常和返回值。
submit 有返回值。适应场景:关心异常和返回值。
线程池关闭的两种方式?
shutdown():平滑关闭。任务执行完毕以后关闭。
行为:
-
停止接收新任务。
-
已提交的任务(包括排队中和正在执行的)会继续执行完成。
-
等所有任务执行完后,线程池才会真正关闭。
shutdownNow():立即关闭。
行为:
-
停止接收新任务。
-
尝试中断正在执行的任务(通过
Thread.interrupt()实现)。 -
返回还未开始执行的任务列表(
List<Runnable>)。
需要注意的是,shutdownNow 不会真正终止正在运行的任务,只是给任务线程发送 interrupt 信号,任务是否能真正终止取决于线程是否响应 InterruptedException。
线程池的核心参数的设置?
CPU密集型:涉及到大量的CPU的计算,为了避免大量的上下文切换,线程数设置为
CPU核心数+1。
IO密集型:涉及到大量的IO操作(网络,数据库数据读取),会出现阻塞的情况,线程设置都多一些,设置为CPU核心数的2倍。
十. 常见的线程池有哪些 ?
线程池自带的四种线程池。
1. 固定大小的线程池(fixedThreadPool):适合固定任务数的场景
2. 带缓存的线程池(shceduleThreadPool)
3. 定时任务线程池
4. 单一的线程池(singleThreadPool)
为啥使用手动创建而不使用自带的线程池?
总: Executors自带的几种线程池,虽然使用简单,但是自带的参数存在一些风险。因此,采用手动创建线程池,来灵活控制线程池的参数。
分: 比如:newfixedThreadPool内部的队列是链表实现的无界队列,可以缓存大量的任务,可能造成OOM现象。newcacheThreadPool在高并发场景下面,可以创建Integer.Max个线程,容易出现OOM现象。
十一. 线程池使用过程中应该注意什么?
1. 合适的线程池的大小
2. 合适的任务队列
3.手动创建线程池
十二. 线程池执行中断电了应该怎么处理?
1. 持久化操作:持久化任务。可以将任务持久化到数据库或者消息队列中,等电恢复后再重新执行。
2. 幂等性操作: 任务幂等性,需要保证任务是幂等的,也就是无论执行多少次,结果都一致。
十二. 线程池怎么捕获异常
1. try catch捕获异常 2. future 的get方法获取异常
十二. 面试线程池带有返回值如何处理
public class FtpUploadManager {
// 模拟从本地服务器读取文件并上传到远端服务器
public static String readAndUpload(String serverIp) {
try {
System.out.println("开始处理服务器: " + serverIp + ",线程:" + Thread.currentThread().getName());
// 模拟读取文件(1秒)
Thread.sleep(1000);
System.out.println("读取文件成功,服务器: " + serverIp);
// 模拟上传文件(1秒)
Thread.sleep(1000);
System.out.println("上传文件成功,服务器: " + serverIp);
return "成功:" + serverIp;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "失败:" + serverIp;
}
}
public static void main(String[] args) {
// 模拟五台服务器的IP
List<String> serverIps = List.of(
"192.168.1.1",
"192.168.1.2",
"192.168.1.3",
"192.168.1.4",
"192.168.1.5"
);
// 自定义线程池(核心线程5,最大线程10)
ExecutorService executor = new ThreadPoolExecutor(
5, 10,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "ftp-thread-" + count.getAndIncrement());
}
},
new ThreadPoolExecutor.AbortPolicy()
);
// 存放每个任务的Future
List<CompletableFuture<String>> futureList = new ArrayList<>();
for (String ip : serverIps) {
// 每个任务封装成CompletableFuture异步提交
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> readAndUpload(ip), executor);
futureList.add(future);
} 在for循环内部通过completetableFuture.supplyAsync()创建线程池异步任务
// 等待所有任务完成
CompletableFuture<Void> all = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
try {
// 超时时间:最多等待6秒
all.get(6, TimeUnit.SECONDS);
// 获取每个任务的结果(非阻塞)
for (CompletableFuture<String> future : futureList) {
System.out.println("执行结果:" + future.getNow());
}
} catch (TimeoutException e) {
System.out.println("任务超时,未全部完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
supplyAsyc(): 创建异步任务
CompletableFuture.allOf等待所有任务执行完成
get(timeout, unit)设置超时时间
实现步骤:主要通过ThreadPoolExecutor,CompletableFuture两个类实现的。
1. 通过new ThreadPoolExecutor()方式创建线程池
2. CompletableFuture.supplyasyc()创建异步任务
3. 并通过一个futurelist来存储每个异步任务的返回结果(future)
4. CompletableFuture.allof()方法保证所有的线程都执行完成
5. CompletableFuture.allof()方法返回的对象(新生成CompletableFuture对象)设置超时时间
6. get(timeout,unit)
7. 最后通过遍历futurelist对象获取每个future中的结果
8. 最后将结果拼凑返回给对方厂家
==================================================================================
十三. 线程池常用的队列
一. LinkedBlockingDeque<T> queue = new LinkedBlockingDeque<>();
1. 双端阻塞队列
2. 线程安全,内部使用reentrantLock实现,多个线程可以并发访问。
阻塞特性:
1. 当队列为空时,take()方法会阻塞直到有元素可有。
2. 当队列为满的时候,put()方法会阻塞直到有空间可用。
可以用于生产者和消费者模型。
生产者时时的生产数据存入任务队列(比如解析大的文件),消费者消费数据。
(为了避免内存爆满,每次大于500条即入库,入库以后清空原来的List)
我不能保证写的每个地方都是对的,但是至少能保证不复制、不黏贴,保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。
我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。
其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。
==================================================================================
更多推荐

所有评论(0)