1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]Java并发常见面试题总结(上)完善

添加虚拟线程到&新增问题:单核 CPU 支持 Java 多线程吗?
This commit is contained in:
Guide 2024-06-28 19:43:09 +08:00
parent 132ef6acbd
commit f899fee784
3 changed files with 53 additions and 25 deletions

View File

@ -63,7 +63,7 @@ QQ 飞车这款戏当时还挺火的,很多 90 后的小伙伴应该比较熟
![](https://oss.javaguide.cn/about-the-author/cf.png)
ps: 回坑 CF 快一年了目前的军衔是到了两颗星中校3了。
ps: 回坑 CF 快一年了,目前的军衔是到了两颗星中校 3 了。
那时候成绩挺差的。这样说吧!我当时在很普通的一个县级市的高中,全年级有 500 来人,我基本都是在 280 名左右。而且,整个初二我都没有学物理,上物理课就睡觉,考试就交白卷。

View File

@ -84,8 +84,6 @@ JDK 1.2 之前Java 线程是基于绿色线程Green Threads实现的
在 Windows 和 Linux 等主流操作系统中Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程。Solaris 系统是一个特例Solaris 系统本身就支持多对多的线程模型HotSpot VM 在 Solaris 上支持多对多和一对一。具体可以参考 R 大的回答: [JVM 中的线程模型是用户级的么?](https://www.zhihu.com/question/23096638/answer/29617153)。
虚拟线程在 JDK 21 顺利转正,关于虚拟线程、平台线程(也就是我们上面提到的 Java 线程)和内核线程三者的关系可以阅读我写的这篇文章:[Java 20 新特性概览](../new-features/java20.md)。
### 请简要描述线程与进程的关系,区别及优缺点?
下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。
@ -230,6 +228,30 @@ new 一个 `Thread`,线程进入了新建状态。调用 `start()`方法,会
- **单核时代**:在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率。 假设只运行了一个 Java 进程的情况,当我们请求 IO 的时候,如果 Java 进程中只有一个线程,此线程被 IO 阻塞则整个进程被阻塞。CPU 和 IO 设备只有一个在运行,那么可以简单地说系统整体效率只有 50%。当使用多线程的时候,一个线程被 IO 阻塞,其他线程还可以继续使用 CPU。从而提高了 Java 进程利用系统资源的整体效率。
- **多核时代**: 多核时代多线程主要是为了提高进程利用多核 CPU 的能力。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,不论系统有几个 CPU 核心,都只会有一个 CPU 核心被利用到。而创建多个线程,这些线程可以被映射到底层多个 CPU 核心上执行,在任务中的多个线程没有资源竞争的情况下,任务执行的效率会有显著性的提高,约等于(单核时执行时间/CPU 核心数)。
### 单核 CPU 支持 Java 多线程吗?
单核 CPU 是支持 Java 多线程的。操作系统通过时间片轮转的方式,将 CPU 的时间分配给不同的线程。尽管单核 CPU 一次只能执行一个任务,但通过快速在多个线程之间切换,可以让用户感觉多个任务是同时进行的。
这里顺带提一下 Java 使用的线程调度方式。
操作系统主要通过两种线程调度方式来管理多线程的执行:
- **抢占式调度Preemptive Scheduling**:操作系统决定何时暂停当前正在运行的线程,并切换到另一个线程执行。这种切换通常是由系统时钟中断(时间片轮转)或其他高优先级事件(如 I/O 操作完成)触发的。这种方式存在上下文切换开销,但公平性和 CPU 资源利用率较好,不易阻塞。
- **协同式调度Cooperative Scheduling**:线程执行完毕后,主动通知系统切换到另一个线程。这种方式可以减少上下文切换带来的性能开销,但公平性较差,容易阻塞。
Java 使用的线程调度是抢占式的。也就是说JVM 本身不负责线程的调度,而是将线程的调度委托给操作系统。操作系统通常会基于线程优先级和时间片来调度线程的执行,高优先级的线程通常获得 CPU 时间片的机会更多。
### 单核 CPU 上运行多个线程效率一定会高吗?
单核 CPU 同时运行多个线程的效率是否会高,取决于线程的类型和任务的性质。一般来说,有两种类型的线程:
1. **CPU 密集型**CPU 密集型的线程主要进行计算和逻辑处理,需要占用大量的 CPU 资源。
2. **IO 密集型**IO 密集型的线程主要进行输入输出操作,如读写文件、网络通信等,需要等待 IO 设备的响应,而不占用太多的 CPU 资源。
在单核 CPU 上,同一时刻只能有一个线程在运行,其他线程需要等待 CPU 的时间片分配。如果线程是 CPU 密集型的,那么多个线程同时运行会导致频繁的线程切换,增加了系统的开销,降低了效率。如果线程是 IO 密集型的,那么多个线程同时运行可以利用 CPU 在等待 IO 时的空闲时间,提高了效率。
因此,对于单核 CPU 来说,如果任务是 CPU 密集型的,那么开很多线程会影响效率;如果任务是 IO 密集型的,那么开很多线程会提高效率。当然,这里的“很多”也要适度,不能超过系统能够承受的上限。
### 使用多线程可能带来什么问题?
并发编程的目的就是为了能提高程序的执行效率进而提高程序的运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
@ -241,14 +263,6 @@ new 一个 `Thread`,线程进入了新建状态。调用 `start()`方法,会
- 线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
- 线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。
### 单核 CPU 上运行多个线程效率一定会高吗?
单核 CPU 同时运行多个线程的效率是否会高取决于线程的类型和任务的性质。一般来说有两种类型的线程CPU 密集型和 IO 密集型。CPU 密集型的线程主要进行计算和逻辑处理,需要占用大量的 CPU 资源。IO 密集型的线程主要进行输入输出操作,如读写文件、网络通信等,需要等待 IO 设备的响应,而不占用太多的 CPU 资源。
在单核 CPU 上,同一时刻只能有一个线程在运行,其他线程需要等待 CPU 的时间片分配。如果线程是 CPU 密集型的,那么多个线程同时运行会导致频繁的线程切换,增加了系统的开销,降低了效率。如果线程是 IO 密集型的,那么多个线程同时运行可以利用 CPU 在等待 IO 时的空闲时间,提高了效率。
因此,对于单核 CPU 来说,如果任务是 CPU 密集型的,那么开很多线程会影响效率;如果任务是 IO 密集型的,那么开很多线程会提高效率。当然,这里的“很多”也要适度,不能超过系统能够承受的上限。
## 死锁
### 什么是线程死锁?
@ -391,4 +405,14 @@ Process finished with exit code 0
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
## 虚拟线程
虚拟线程在 Java 21 正式发布,这是一项重量级的更新。我写了一篇文章来总结虚拟线程常见的问题:[虚拟线程常见问题总结](./virtual-thread.md),包含下面这些问题:
1. 什么是虚拟线程?
2. 虚拟线程和平台线程有什么关系?
3. 虚拟线程有什么优点和缺点?
4. 如何创建虚拟线程?
5. 虚拟线程的底层原理是什么?
<!-- @include: @article-footer.snippet.md -->

View File

@ -1,5 +1,5 @@
---
title: 虚拟线程极简入门
title: 虚拟线程常见问题总结
category: Java
tag:
- Java并发
@ -27,26 +27,25 @@ tag:
### 优点
- 非常轻量级:可以在单个线程中创建成百上千个虚拟线程而不会导致过多的线程创建和上下文切换。
- 简化异步编程: 虚拟线程可以简化异步编程使代码更易于理解和维护。它可以将异步代码编写得更像同步代码避免了回调地狱Callback Hell
- 减少资源开销: 相比于操作系统线程,虚拟线程的资源开销更小。本质上是提高了线程的执行效率,从而减少线程资源的创建和上下文切换
- **非常轻量级**:可以在单个线程中创建成百上千个虚拟线程而不会导致过多的线程创建和上下文切换。
- **简化异步编程** 虚拟线程可以简化异步编程使代码更易于理解和维护。它可以将异步代码编写得更像同步代码避免了回调地狱Callback Hell
- **减少资源开销** 由于虚拟线程是由 JVM 实现的,它能够更高效地利用底层资源,例如 CPU 和内存。虚拟线程的上下文切换比平台线程更轻量,因此能够更好地支持高并发场景
### 缺点
- 不适用于计算密集型任务: 虚拟线程适用于 I/O 密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要 CPU 资源作为支持。
- 依赖于语言或库的支持: 协程需要编程语言或库提供支持。不是所有编程语言都原生支持协程。比如 Java 实现的虚拟线程。
- **不适用于计算密集型任务** 虚拟线程适用于 I/O 密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要 CPU 资源作为支持。
- **与某些第三方库不兼容** 虽然虚拟线程设计时考虑了与现有代码的兼容性,但某些依赖平台线程特性的第三方库可能不完全兼容虚拟线程。
## 四种创建虚拟线程的方法
Java 21 已经正式支持虚拟线程,大家可以在官网下载使用,在使用上官方为了降低使用门槛,尽量复用原有的 `Thread` 类,让大家可以更加平滑的使用。
## 如何创建虚拟线程?
官方提供了以下四种方式创建虚拟线程:
1. 使用 `Thread.startVirtualThread()` 创建
2. 使用 `Thread.ofVirtual()` 创建
3. 使用 `ThreadFactory` 创建
4. 使用 `Executors.newVirtualThreadPerTaskExecutor()`创建
#### 使用 Thread.startVirtualThread()创建
**1、使用 `Thread.startVirtualThread()` 创建**
```java
public class VirtualThreadTest {
@ -64,7 +63,7 @@ static class CustomThread implements Runnable {
}
```
#### 使用 Thread.ofVirtual()创建
**2、使用 `Thread.ofVirtual()` 创建**
```java
public class VirtualThreadTest {
@ -85,7 +84,7 @@ static class CustomThread implements Runnable {
}
```
#### 使用 ThreadFactory 创建
**3、使用 `ThreadFactory` 创建**
```java
public class VirtualThreadTest {
@ -105,7 +104,7 @@ static class CustomThread implements Runnable {
}
```
#### 使用 Executors.newVirtualThreadPerTaskExecutor()创建
**4、使用`Executors.newVirtualThreadPerTaskExecutor()`创建**
```java
public class VirtualThreadTest {
@ -227,6 +226,11 @@ totalMillis2865ms
- 可以看到在密集 IO 的场景下,需要创建大量的平台线程异步处理才能达到虚拟线程的处理速度。
- 因此,在密集 IO 的场景,虚拟线程可以大幅提高线程的执行效率,减少线程资源的创建以及上下文切换。
- 吐槽:虽然虚拟线程我很想用,但是我 Java8 有机会升级到 Java21 吗?呜呜
**注意**:有段时间 JDK 一直致力于 Reactor 响应式编程来提高 Java 性能,但响应式编程难以理解、调试、使用,最终又回到了同步编程,最终虚拟线程诞生。
## 虚拟线程的底层原理是什么?
如果你想要详细了解虚拟线程实现原理,推荐一篇文章:[虚拟线程 - VirtualThread 源码透视](https://www.cnblogs.com/throwable/p/16758997.html)。
面试一般是不会问到这个问题的,仅供学有余力的同学进一步研究学习。