首页 简历|笔试面试

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

  • 25年9月4日 发布
  • 27.07MB 共177页
面渣逆袭并发编程篇V2.1面渣逆袭并发编程篇V2.1面渣逆袭并发编程篇V2.1面渣逆袭并发编程篇V2.1面渣逆袭并发编程篇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

开通会员 本次下载免费

所有资料全部免费下载! 推荐用户付费下载获取返佣积分! 积分可以兑换商品!
普通用户: 6.48元
网站会员:
本次下载免费

开通网站会员 享专属特权

  • 会员可免费

    下载全部资料!

  • 推荐用户下载

    获取返佣积分!

  • 积分可以

    兑换商品!

一键复制 下载文档 联系客服