面渣逆袭并发编程篇V2.1




⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
前⾔ No. 1 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
前⾔
4 万字 145 张⼿绘图,详解 71 道 Java 多线程⾯试⾼频题(让天下没有难背的⼋股),⾯渣背会这些并发编程⼋股
⽂,这次吊打⾯试官,我觉得稳了(⼿动 dog)。
第⼀版作者是⼆哥编程星球的嘉宾三分恶,第⼆版由⼆哥结合球友们的⾯经+技术派+PmHub+mydb 的项⽬进⾏全
新升级。更适合拿来背诵突击⾯试+底层原理理解。
亮⽩版本更适合拿出来打印,这也是很多学⽣党喜欢的⽅式,打印出来背诵的效率会更⾼。
2025 年 01 ⽉ 22 ⽇开始着⼿第⼆版更新。
对于⾼频题,会标注在《Java ⾯试指南(付费)》中出现的位置,哪家公司,原题是什么,并且会加 ,⽬
录⼀⽬了然;如果你想节省时间的话,可以优先背诵这些题⽬,尽快做到知彼知⼰,百战不殆。
区分⼋股精华回答版本和原理底层解释,让⼤家知其然知其所以然,同时⼜能做到⾯试时的⾼效回答。
结合项⽬(技术派、pmhub)来组织语⾔,让⾯试官最⼤程度感受到你的诚意,⽽不是机械化的背诵。
修复第⼀版中出现的问题,包括球友们的私信反馈,⽹站留⾔区的评论,以及 GitHub 仓库中的 issue,让这
份⾯试指南更加完善。
增加⼆哥编程星球的球友们拿到的⼀些 offer,对⾯渣逆袭的感谢,以及对简历修改的⼀些认可,以此来激励
⼤家,给⼤家更多信⼼。
No. 2 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
优化排版,增加⼿绘图,重新组织答案,使其更加⼝语化,从⽽更贴近⾯试官的预期。
码,关注⼆哥的公众号,回复【222】即可拉取最新版本。
No. 3 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
当然了,请允许我的⼀点点私⼼,那就是星球的 PDF 版本会⽐公众号早⼀个⽉时间,毕竟星球⽤户都付费过了,
我有必要让他们先享受到⼀点点福利。相信⼤家也都能理解,毕竟在线版是免费的,CDN、服务器、域名、OSS
等等都是需要成本的。
更别说我付出的时间和精⼒了,⼤家觉得有帮助还请给个⼝碑,让你身边的同事、同学都能受益到。
我把⼆哥的 Java 进阶之路、JVM 进阶之路、并发编程进阶之路,以及所有⾯渣逆袭的版本都放进来了,涵盖 Java
基础、Java集合、Java并发、JVM、Spring、MyBatis、计算机⽹络、操作系统、MySQL、Redis、RocketMQ、分
布式、微服务、设计模式、Linux 等 16 个⼤的主题,共有 40 多万字,2000+张⼿绘图,可以说是诚意满满。
展示⼀下暗⿊版本的 PDF 吧,排版清晰,字体优雅,更加适合夜服,晚上看会更舒服⼀点。
No. 4 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
基础
1.并⾏跟并发有什么区别?
并⾏是多核 CPU 上的多任务处理,多个任务在同⼀时间真正地同时执⾏。
并发是单核 CPU 上的多任务处理,多个任务在同⼀时间段内交替执⾏,通过时间⽚轮转实现交替执⾏,⽤于
解决 IO 密集型任务的瓶颈。
No. 5 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
举个例⼦,就好像我们去⻝堂打饭,并⾏就是每个⼈对应⼀个阿姨,同时打饭;⽽并发就是⼀个阿姨,轮流给每个
⼈打饭,假如有个⼈磨磨唧唧,阿姨就会吆喝下⼀个⼈,这样就能提⾼⻝堂的打饭效率。
你是如何理解线程安全的?
推荐阅读:多线程带来了哪些问题?
如果⼀段代码块或者⼀个⽅法被多个线程同时执⾏,还能够正确地处理共享数据,那么这段代码块或者这个⽅法就
是线程安全的。
可以从三个要素来确保线程安全:
①、原⼦性:⼀个操作要么完全执⾏,要么完全不执⾏,不会出现中间状态。
No. 6 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
可以通过同步关键字 synchronized 或原⼦操作,如 AtomicInteger 来保证原⼦性。
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原⼦操作
②、可⻅性:当⼀个线程修改了共享变量,其他线程能够⽴即看到变化。
No. 7 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
可以通过 volatile 关键字来保证可⻅性。
private volatile String itwanger = "沉默王⼆";
③、有序性:要确保线程不会因为死锁、饥饿、活锁等问题导致⽆法继续执⾏。
No. 8 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
1. Java ⾯试指南(付费)收录的华为 OD ⾯经同学 1 ⼀⾯⾯试原题:对于多线程编程的了解?
2. Java ⾯试指南(付费)收录的快⼿⾯经同学 1 部⻔主站技术部⾯试原题:你对线程安全的理解是什么?
memo:2025 年 1 ⽉ 22 ⽇修改⾄此。
2.说说进程和线程的区别?
推荐阅读:进程与线程的区别是什么?
进程说简单点就是我们在电脑上启动的⼀个个应⽤。它是操作系统分配资源的最⼩单位。
线程是进程中的独⽴执⾏单元。多个线程可以共享同⼀个进程的资源,如内存;每个线程都有⾃⼰独⽴的栈和寄存
器。
No. 9 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
如何理解协程?
协程被视为⽐线程更轻量级的并发单元,可以在单线程中实现并发执⾏,由我们开发者显式调度。
协程是在⽤户态进⾏调度的,避免了线程切换时的内核态开销。
Java ⾃身是不⽀持携程的,我们可以使⽤ Quasar、Kotlin 等框架来实现协程。
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
线程间是如何进⾏通信的?
原则上可以通过消息传递和共享内存两种⽅法来实现。Java 采⽤的是共享内存的并发模型。
这个模型被称为 Java 内存模型,简写为 JMM,它决定了⼀个线程对共享变量的写⼊,何时对另外⼀个线程可⻅。
当然了,本地内存是 JMM 的⼀个抽象概念,并不真实存在。
⽤⼀句话来概括就是:共享变量存储在主内存中,每个线程的私有本地内存,存储的是这个共享变量的副本。
No. 10 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
线程 A 与线程 B 之间如要通信,需要要经历 2 个步骤:
线程 A 把本地内存 A 中的共享变量副本刷新到主内存中。
线程 B 到主内存中读取线程 A 刷新过的共享变量,再同步到⾃⼰的共享变量副本中。
1. Java ⾯试指南(付费)收录的字节跳动商业化⼀⾯的原题:进程和线程区别,线程共享内存和进程共享
内存的区别
2. Java ⾯试指南(付费)收录的⼩⽶春招同学 K ⼀⾯⾯试原题:协程和线程和进程的区别
3. Java ⾯试指南(付费)收录的字节跳动⾯经同学 1 Java 后端技术⼀⾯⾯试原题:线程和进程有什么区
别?
No. 11 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
4. Java ⾯试指南(付费)收录的华为 OD ⾯经同学 1 ⼀⾯⾯试原题:对于多线程编程的了解?
5. Java ⾯试指南(付费)收录的美团⾯经同学 2 Java 后端技术⼀⾯⾯试原题:进程和线程的区别?
6. Java ⾯试指南(付费)收录的华为⾯经同学 9 Java 通⽤软件开发⼀⾯⾯试原题:进程和线程的区别
7. Java ⾯试指南(付费)收录的 ⼩公司⾯经合集好未来测开⾯经同学 3 测开⼀⾯⾯试原题:进程和线程
的区别
8. Java ⾯试指南(付费)收录的招商银⾏⾯经同学 6 招银⽹络科技⾯试原题:进程和线程的区别?
9. Java ⾯试指南(付费)收录的⽤友⾯试原题:线程和进程的区别
10. Java ⾯试指南(付费)收录的vivo ⾯经同学 10 技术⼀⾯⾯试原题:线程的概念,线程有哪些状态
11. Java ⾯试指南(付费)收录的海康威视同学 4⾯试原题:对协程的了解,为什么协程⽐线程还有更低的
资源消耗
memo:2025 年 1 ⽉ 23 ⽇修改⾄此。
3.说说线程有⼏种创建⽅式?
推荐阅读:室友打了⼀把王者就学会了 Java 多线程
有三种,分别是继承 Thread 类、实现 Runnable 接⼝、实现 Callable 接⼝。
第⼀种需要重写⽗类 Thread 的 run() ⽅法,并且调⽤ start() ⽅法启动线程。
class ThreadTask extends Thread {
public void run() {
System.out.println("看完⼆哥的 Java 进阶之路,上岸了!");
}
public static void main(String[] args) {
ThreadTask task = new ThreadTask();
task.start();
}
}
这种⽅法的缺点是,如果 ThreadTask 已经继承了另外⼀个类,就不能再继承 Thread 类了,因为 Java 不⽀持多重
继承。
第⼆种需要重写 Runnable 接⼝的 run() ⽅法,并将实现类的对象作为参数传递给 Thread 对象的构造⽅法,最
后调⽤ start() ⽅法启动线程。
No. 12 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
class RunnableTask implements Runnable {
public void run() {
System.out.println("看完⼆哥的 Java 进阶之路,上岸了!");
}
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
Thread thread = new Thread(task);
thread.start();
}
}
这种⽅法的优点是可以避免 Java 的单继承限制,并且更符合⾯向对象的编程思想,因为 Runnable 接⼝将任务代
码和线程控制的代码解耦了。
第三种需要重写 Callable 接⼝的 call() ⽅法,然后创建 FutureTask 对象,参数为 Callable 实现类的对象;紧
接着创建 Thread 对象,参数为 FutureTask 对象,最后调⽤ start() ⽅法启动线程。
class CallableTask implements Callable<String> {
public String call() {
return "看完⼆哥的 Java 进阶之路,上岸了!";
}
public static void main(String[] args) throws ExecutionException,
InterruptedException {
CallableTask task = new CallableTask();
FutureTask<String> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
这种⽅法的优点是可以获取线程的执⾏结果。
⼀个 8G 内存的系统最多能创建多少个线程?
推荐阅读:深⼊理解 JVM 的运⾏时数据区
理论上⼤约 8000 个。
创建线程的时候,⾄少需要分配⼀个虚拟机栈,在 64 位操作系统中,默认⼤⼩为 1M,因此⼀个线程⼤约需要 1M
的内存。
但 JVM、操作系统本身的运⾏就要占⼀定的内存空间,所以实际上可以创建的线程数远⽐ 8000 少。
详细解释⼀下。
可以通过 java -XX:+PrintFlagsFinal -version | grep ThreadStackSize 命令查看 JVM 栈的默认⼤⼩。
No. 13 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
其中 ThreadStackSize 的单位是 KB,也就是说默认的 JVM 栈⼤⼩是 1024 KB,也就是 1M。
启动⼀个 Java 程序,你能说说⾥⾯有哪些线程吗?
⾸先是 main 线程,这是程序执⾏的⼊⼝。
然后是垃圾回收线程,它是⼀个后台线程,负责回收不再使⽤的对象。
还有编译器线程,⽐如 JIT,负责把⼀部分热点代码编译后放到 codeCache 中。
No. 14 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
可以通过下⾯的代码进⾏检测:
class ThreadLister {
public static void main(String[] args) {
// 获取所有线程的堆栈跟踪
Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
for (Thread thread : threads.keySet()) {
System.out.println("Thread: " + thread.getName() + " (ID=" + thread.getId() +
")");
}
}
}
结果如下所示:
No. 15 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
Thread: Monitor Ctrl-Break (ID=5)
Thread: Reference Handler (ID=2)
Thread: main (ID=1)
Thread: Signal Dispatcher (ID=4)
Thread: Finalizer (ID=3)
简单解释下:
Thread: main (ID=1) - 主线程,Java 程序启动时由 JVM 创建。
Thread: Reference Handler (ID=2) - 这个线程是⽤来处理引⽤对象的,如软引⽤、弱引⽤和虚引⽤。负
责清理被 JVM 回收的对象。
Thread: Finalizer (ID=3) - 终结器线程,负责调⽤对象的 finalize ⽅法。对象在垃圾回收器标记为可回
收之前,由该线程执⾏其 finalize ⽅法,⽤于执⾏特定的资源释放操作。
Thread: Signal Dispatcher (ID=4) - 信号调度线程,处理来⾃操作系统的信号,将它们转发给 JVM 进⾏
进⼀步处理,例如响应中断、停⽌等信号。
Thread: Monitor Ctrl-Break (ID=5) - 监视器线程,通常由⼀些特定的 IDE 创建,⽤于在开发过程中监
控和管理程序执⾏或者处理中断。
1. Java ⾯试指南(付费)收录的字节跳动⾯经同学 1 Java 后端技术⼀⾯⾯试原题:有多少种实现线程的
⽅法?
2. Java ⾯试指南(付费)收录的农业银⾏同学 1 ⾯试原题:实现线程的⽅式和区别
3. Java ⾯试指南(付费)收录的农业银⾏⾯经同学 3 Java 后端⾯试原题:说说线程的创建⽅法
4. Java ⾯试指南(付费)收录的⼩公司⾯经合集同学 1 Java 后端⾯试原题:线程创建的⽅式?Runable
和 Callable 有什么区别?
5. Java ⾯试指南(付费)收录的阿⾥⾯经同学 5 阿⾥妈妈 Java 后端技术⼀⾯⾯试原题:⼀个 8G 内存的
系统最多能创建多少线程?(奇怪的问题,答了⼀些 pcb、⻚表、虚拟机栈什么的)启动⼀个 Java 程
序,你能说说⾥⾯有哪些线程吗?
6. Java ⾯试指南(付费)收录的招商银⾏⾯经同学 6 招银⽹络科技⾯试原题:如何创建线程?
7. Java ⾯试指南(付费)收录的百度⾯经同学 1 ⽂⼼⼀⾔ 25 实习 Java 后端⾯试原题:java 如何创建线
程?每次都要创建新线程来实现异步操作,很繁琐,有了解线程池吗?
8. Java ⾯试指南(付费)收录的美团⾯经同学 4 ⼀⾯⾯试原题:平时怎么使⽤多线程
memo:2025 年 1 ⽉ 24 ⽇修改⾄此。
4.调⽤ start ⽅法时会执⾏ run ⽅法,那怎么不直接调⽤ run⽅法?
调⽤ start() 会创建⼀个新的线程,并异步执⾏ run() ⽅法中的代码。
直接调⽤ run() ⽅法只是⼀个普通的同步⽅法调⽤,所有代码都在当前线程中执⾏,不会创建新线程。没有新的
线程创建,也就达不到多线程并发的⽬的。
通过敲代码体验⼀下。
No. 16 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
class MyThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 正确的⽅式,创建⼀个新线程,并在新线程中执⾏ run()
t1.run(); // 仅在主线程中执⾏ run(),没有创建新线程
}
}
来看输出结果:
main
Thread-0
也就是说,调⽤ start() ⽅法会通知 JVM,去调⽤底层的线程调度机制来启动新线程。
调⽤ start() 后,线程进⼊就绪状态,等待操作系统调度;⼀旦调度执⾏,线程会执⾏其 run() ⽅法中的代
码。
1. Java ⾯试指南(付费)收录的⼩公司⾯经合集同学 1 Java 后端⾯试原题:启动⼀个线程是 run()还是
start()?
2. Java ⾯试指南(付费)收录的百度⾯经同学 1 ⽂⼼⼀⾔ 25 实习 Java 后端⾯试原题:java 如何启动多
线程,有哪些⽅式?
No. 17 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
3. ⼆哥编程星球球友枕云眠美团 AI ⾯试原题:java 线程操作中的 start 和 run ⽅法区别是什么
memo:2025 年 1 ⽉ 26 ⽇修改⾄此。
5.线程有哪些常⽤的调度⽅法?
⽐如说 start ⽅法⽤于启动线程并让操作系统调度执⾏;sleep ⽅法⽤于让当前线程休眠⼀段时间;wait ⽅法会让
当前线程等待,notify 会唤醒⼀个等待的线程。
说说wait⽅法和notify⽅法?
当线程 A 调⽤共享对象的 wait() ⽅法时,线程 A 会被阻塞挂起,直到:
线程 B 调⽤了共享对象的 notify() ⽅法或者 notifyAll() ⽅法;
其他线程调⽤线程 A 的 interrupt() ⽅法,导致线程 A 抛出 InterruptedException 异常。
线程 A 调⽤共享对象的 wait(timeout) ⽅法后,没有在指定的 timeout 时间内被其它线程唤醒,那么这个⽅法会
因为超时⽽返回。
当线程 A 调⽤共享对象的 notify() ⽅法后,会唤醒⼀个在这个共享对象上调⽤ wait 系列⽅法被挂起的线程。
共享对象上可能会有多个线程在等待,具体唤醒哪个线程是随机的。
No. 18 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
如果调⽤的是 notifyAll ⽅法,会唤醒所有在这个共享变量上调⽤ wait 系列⽅法⽽被挂起的线程。
说说 sleep ⽅法?
当线程 A 调⽤了 Thread 的 sleep ⽅法后,线程 A 会暂时让出指定时间的执⾏权。
指定的睡眠时间到了后该⽅法会正常返回,接着参与 CPU 调度,获取到 CPU 资源后可以继续执⾏。
说说yield⽅法?
yield() ⽅法的⽬的是让当前线程让出 CPU 使⽤权,回到就绪状态。但是线程调度器可能会忽略。
说说interrupt⽅法?
推荐阅读:interrupt ⽅法
interrupt() ⽅法⽤于通知线程停⽌,但不会直接终⽌线程,需要线程⾃⾏处理中断标志。
常与 isInterrupted() 或 Thread.interrupted() 配合使⽤。
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Running");
}
System.out.println("Interrupted");
});
thread.start();
thread.interrupt(); // 中断线程
说说 stop ⽅法?
stop ⽅法⽤来强制停⽌线程,⽬前已经处于废弃状态,因为 stop ⽅法可能会在不⼀致的状态下释放锁,破坏对象
的⼀致性。
No. 19 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
1. Java ⾯试指南(付费)收录的帆软同学 3 Java 后端⼀⾯的原题:怎么停⽌⼀个线程,interrupt 和 stop
区别
memo:2025 年 1 ⽉ 27 ⽇修改⾄此。
6.线程有⼏种状态?
6 种。
new 代表线程被创建但未启动;runnable 代表线程处于就绪或正在运⾏状态,由操作系统调度;blocked 代表线
程被阻塞,等待获取锁;waiting 代表线程等待其他线程的通知或中断;timed_waiting 代表线程会等待⼀段时
间,超时后⾃动恢复;terminated 代表线程执⾏完毕,⽣命周期结束。
No. 20 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
也就是说,线程的⽣命周期可以分为五个主要阶段:新建、就绪、运⾏、阻塞和终⽌。线程在运⾏过程中会根据状
态的变化在这些阶段之间切换。
class ThreadStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000); // TIMED_WAITING
synchronized (ThreadStateExample.class) {
ThreadStateExample.class.wait(); // WAITING
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println("State after creation: " + thread.getState()); // NEW
thread.start();
System.out.println("State after start: " + thread.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("State while sleeping: " + thread.getState()); //
TIMED_WAITING
synchronized (ThreadStateExample.class) {
No. 21 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
ThreadStateExample.class.notify(); // 唤醒线程
}
thread.join();
System.out.println("State after termination: " + thread.getState()); //
TERMINATED
}
}
⽤⼀个表格来做个总结:
状态 说明
当线程被创建后,如通过 new Thread() ,它处于新建状态。此时,线程已经被分配了必
NEW
要的资源,但还没有开始执⾏。
当调⽤线程的 start() ⽅法后,线程进⼊可运⾏状态。在这个状态下,线程可能正在运
RUNNABLE
⾏也可能正在等待获取 CPU 时间⽚,具体取决于线程调度器的调度策略。
线程在试图获取⼀个锁以进⼊同步块/⽅法时,如果锁被其他线程持有,线程将进⼊阻塞
BLOCKED
状态,直到它获取到锁。
线程进⼊等待状态是因为调⽤了如下⽅法之⼀: Object.wait() 或
WAITING LockSupport.park() 。在等待状态下,线程需要其他线程显式地唤醒,否则不会⾃动
执⾏。
当线程调⽤带有超时参数的⽅法时,如 Thread.sleep(long millis) 、
TIME_WAITING Object.wait(long timeout) 或 LockSupport.parkNanos() ,它将进⼊超时等待状
态。线程在指定的等待时间过后会⾃动返回可运⾏状态。
当线程的 run() ⽅法执⾏完毕后,或者因为⼀个未捕获的异常终⽌了执⾏,线程进⼊终
TERMINATED
⽌状态。⼀旦线程终⽌,它的⽣命周期结束,不能再被重新启动。
如何强制终⽌线程?
第⼀步,调⽤线程的 interrupt() ⽅法,请求终⽌线程。
第⼆步,在线程的 run() ⽅法中检查中断状态,如果线程被中断,就退出线程。
class MyTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("Running...");
Thread.sleep(1000); // 模拟⼯作
} catch (InterruptedException e) {
// 捕获中断异常后,重置中断状态
Thread.currentThread().interrupt();
System.out.println("Thread interrupted, exiting...");
break;
No. 22 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyTask());
thread.start();
Thread.sleep(3000); // 主线程等待3秒
thread.interrupt(); // 请求终⽌线程
}
}
中断结果:
1. Java ⾯试指南(付费)收录的招商银⾏⾯经同学 6 招银⽹络科技⾯试原题:线程的⽣命周期和状态?
2. Java ⾯试指南(付费)收录的快⼿同学 2 ⼀⾯⾯试原题:线程有哪些状态?
3. Java ⾯试指南(付费)收录的 OPPO ⾯经同学 1 ⾯试原题:Java⾥线程的⽣命周期
4. Java ⾯试指南(付费)收录的同学 D ⼩⽶⼀⾯原题:线程的⽣命周期
7.什么是线程上下⽂切换?
线程上下⽂切换是指 CPU 从⼀个线程切换到另⼀个线程执⾏时的过程。
在线程切换的过程中,CPU 需要保存当前线程的执⾏状态,并加载下⼀个线程的上下⽂。
之所以要这样,是因为 CPU 在同⼀时刻只能执⾏⼀个线程,为了实现多线程并发执⾏,需要不断地在多个线程之
间切换。
No. 23 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
为了让⽤户感觉多个线程是在同时执⾏的, CPU 资源的分配采⽤了时间⽚轮转的⽅式,线程在时间⽚内占⽤ CPU
执⾏任务。当线程使⽤完时间⽚后,就会让出 CPU 让其他线程占⽤。
线程可以被多核调度吗?
多核处理器提供了并⾏执⾏多个线程的能⼒。每个核⼼可以独⽴执⾏⼀个或多个线程,操作系统的任务调度器会根
据策略和算法,如优先级调度、轮转调度等,决定哪个线程何时在哪个核⼼上运⾏。
1. Java ⾯试指南(付费)收录的字节跳动同学 7 Java 后端实习⼀⾯的原题:线程可以被多核调度吗?
No. 24 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
2. Java ⾯试指南(付费)收录的携程⾯经同学 1 Java 后端技术⼀⾯⾯试原题:线程上下⽂切换(我答的
内核态和⽤户态切换时机,和切换需要加载哪些内容)
8.守护线程了解吗?
了解,守护线程是⼀种特殊的线程,它的作⽤是为其他线程提供服务。
Java 中的线程分为两类,⼀种是守护线程,另外⼀种是⽤户线程。
JVM 启动时会调⽤ main ⽅法,main ⽅法所在的线程就是⼀个⽤户线程。在 JVM 内部,同时还启动了很多守护线
程,⽐如垃圾回收线程。
守护线程和⽤户线程有什么区别呢?
区别之⼀是当最后⼀个⾮守护线程束时, JVM 会正常退出,不管当前是否存在守护线程,也就是说守护线程是否
结束并不影响 JVM 退出。
换⽽⾔之,只要有⼀个⽤户线程还没结束,正常情况下 JVM 就不会退出。
9.线程间有哪些通信⽅式?
线程之间传递信息的⽅式有多种,⽐如说使⽤ volatile 和 synchronized 关键字共享对象、使⽤ wait() 和
notify() ⽅法实现⽣产者-消费者模式、使⽤ Exchanger 进⾏数据交换、使⽤ Condition 实现线程间的协调等。
简单说说 volatile 和 synchronized 的使⽤⽅式?
多个线程可以通过 volatile 和 synchronized 关键字访问和修改同⼀个对象,从⽽实现信息的传递。
关键字 volatile 可以⽤来修饰成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,并同步刷新回共
享内存,保证所有线程对变量访问的可⻅性。
关键字 synchronized 可以修饰⽅法,或者同步代码块,确保多个线程在同⼀个时刻只有⼀个线程在执⾏⽅法或代
码块。
class SharedObject {
private String message;
private boolean hasMessage = false;
public synchronized void writeMessage(String message) {
while (hasMessage) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
this.message = message;
hasMessage = true;
notifyAll();
}
public synchronized String readMessage() {
while (!hasMessage) {
No. 25 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
hasMessage = false;
notifyAll();
return message;
}
}
public class Main {
public static void main(String[] args) {
SharedObject sharedObject = new SharedObject();
Thread writer = new Thread(() -> {
sharedObject.writeMessage("Hello from Writer!");
});
Thread reader = new Thread(() -> {
String message = sharedObject.readMessage();
System.out.println("Reader received: " + message);
});
writer.start();
reader.start();
}
}
wait() 和 notify() ⽅法的使⽤⽅式了解吗?
⼀个线程调⽤共享对象的 wait() ⽅法时,它会进⼊该对象的等待池,释放已经持有的锁,进⼊等待状态。
⼀个线程调⽤ notify() ⽅法时,它会唤醒在该对象等待池中等待的⼀个线程,使其进⼊锁池,等待获取锁。
class MessageBox {
private String message;
private boolean empty = true;
public synchronized void produce(String message) {
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = false;
this.message = message;
notifyAll();
}
No. 26 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
public synchronized String consume() {
while (empty) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = true;
notifyAll();
return message;
}
}
public class Main {
public static void main(String[] args) {
MessageBox box = new MessageBox();
Thread producer = new Thread(() -> {
box.produce("Message from producer");
});
Thread consumer = new Thread(() -> {
String message = box.consume();
System.out.println("Consumer received: " + message);
});
producer.start();
consumer.start();
}
}
Condition 也提供了类似的⽅法, await() 负责阻塞、 signal() 和 signalAll() 负责通知。
通常与锁 ReentrantLock ⼀起使⽤,为线程提供了⼀种等待某个条件成真的机制,并允许其他线程在该条件变化
时通知等待线程。
Exchanger 的使⽤⽅式了解吗?
Exchanger 是⼀个同步点,可以在两个线程之间交换数据。⼀个线程调⽤ exchange() ⽅法,将数据传递给另⼀
个线程,同时接收另⼀个线程的数据。
class Main {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread thread1 = new Thread(() -> {
try {
String message = "Message from thread1";
String response = exchanger.exchange(message);
System.out.println("Thread1 received: " + response);
No. 27 / 177
⾯渣逆袭并发编程篇V2-让天下所有的⾯渣都能逆袭
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread thread2 = new Thread