面试官:Java线程可以无限创建吗?

哈喽,大家好,我是世杰。
本次给大家介绍一下操作系统线程和Java的线程以及二者的关联
1. 面试连环call
- Java线程可以无限创建吗?
- Java线程和操作系统线程有什么关联?
- 操作系统为什么要区分内核态和用户态?
要想解答这些问题,我们要先从操作系统线程开始说起,让我们开始吧
2. 操作系统线程
2.1 内核态和用户态
根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。
内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。

那为什么要区分用户态和内核态呢?
在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令 。
如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。
2.2 用户态线程
在早期的操作系统中,所有的线程都是在用户态下实现,操作系统只能调度线程所属的进程,而无法调度线程。
在这种模型下,用户需要自己定义线程的数据结构、创建、销毁、调度和维护等,这些线程运行在某个进程内,操作系统直接对进程进行调度。

『优点』:
- 即使操作系统原生不支持线程,我们也可以通过库函数来支持线程
- 线程的调度只发生在用户态,避免了操作系统从内核态到用户态的转换开销。
『缺点』:
- 由于操作系统无法调度线程,CPU 的时间片切换是以进程为维度的,如果进程中某个线程进行了耗时比较长的操作,那么由于用户态中没有时钟中断机制,就会导致此进程中的其它线程因为得不到 CPU 资源而长时间的持续等待;
- 如果某个线程进行系统调用时比如缺页中断而导致了线程阻塞,此时操作系统也会阻塞整个进程,即使这个进程中其它线程还在工作。
2.3 内核态线程
现代操作系统,包括 Windows、Linux、Mac OS X 和 Solaris 等,都支持内核线程。线程运行在内核空间,直接由内核负责,由内核来完成调度。
此时我们可以直接使用操作系统中已经内置好的线程,线程的创建、销毁、调度和维护等,都直接由操作系统的内核来实现,我们只需要使用系统调用就好了,不需要像用户级线程那样自己设计线程调度。

内核线程和用户线程的对应关系并不完全是1对1,其关联模式有三种
2.4 线程模型
多对一线程模型:
多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。这样,极大地减少了创建内核态线程的成本,但是线程不可以并行。因此,这种模型现在基本上用的很少。

『优点』:
- 用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换。使线程的创建、调度、同步等非常快。
『缺点』:
- 由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行。
- 内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的优先级调度等操作
一对一线程模型:
该模型为每个用户态的线程分配一个单独的内核态线程,在这种情况下,每个用户态都需要通过系统调用创建一个绑定的内核线程。 这种模型允许所有线程并行执行,能够充分利用多核优势。目前 Linux 中的线程、OpenJDK Java 线程等采用的都是一对一线程模型。每一个JVM线程,都有一个对应的内核线程。

『优点』:
- 解决了多对一模型的阻塞和调度问题
- 实现起来较为简单
『缺点』:
- 每创建一个用户线程,相应地就需要创建一个内核线程,开销较大,因此需要限制整个系统的线程数量。
- 对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换。
多对多线程模型:
这种模式下会为 n 个用户态线程分配 m 个内核态线程。m 通常小于 n。一种可行的策略是将 m 设置为核数。这种多对多的关系,减少了内核线程,同时也保证了多核心并行。多对多模型中线程的调度需要由内核态和用户态一起来实现,例如线程间同步需要用户态和内核态共同实现。用户态和内核态的分工合作导致实现该模型非常复杂。
PS: Linux多线程模型曾经也想使用该模型,但它太复杂,要对内核进行大范围改动,所以还是采用了一对一的模型!

『优点』:
- 多对多模型将任意数量的用户线程复用到相同或更少数量的内核线程上,结合了一对一和多对一模型的最佳特性
- 用户对创建的线程数没有限制
『缺点』:
- 实现起来非常复杂
3. Java 线程
3.1 线程库
在进入 Java 线程主题之前,有必要讲解一下线程库 Thread library 的概念。
线程库就是为开发人员提供创建和管理线程的一套 API。线程库不仅可以在用户空间中实现,还可以在内核空间中实现。前者涉及仅在用户空间内实现的 API 函数,没有内核支持。后者涉及系统调用,也就是说调用库中的一个 API 函数将会导致对内核的系统调用,并且需要具有线程库支持的内核。
下面简单介绍下三个主要的线程库:
POSIX线程:是[POSIX]的[线程]标准,定义了创建和操纵线程的一套[API]。实现POSIX线程标准的库常被称作pthreads,一般用于[Unix-like] POSIX系统,如[Linux]、 [Solaris]。
Win32 线程:用于 Window 操作系统的内核级线程库
Java 线程:Java 线程 API 通常采用宿主系统的线程库来实现,也就是说在 Win 系统上,Java 线程 API 通常采用 Win API 来实现,在 UNIX 类系统上,采用 Pthread 来实现。
3.1 Java线程模型
在 JDK 1.2 之前,Java 线程是基于称为 "绿色线程"(Green Threads)的用户级线程实现的,JVM 开发了自己的一套线程库或者说线程管理机制。
在 JDK 1.2 及以后,JVM 选择了更加稳定且方便使用的操作系统原生的内核级线程,通过系统调用,将线程的调度交给了操作系统内核。而对于不同的操作系统来说,它们本身的设计思路基本上是完全不一样的,因此它们各自对于线程的设计也存在种种差异,所以 JVM 中明确声明了:虚拟机中的线程状态,不反应任何操作系统中的线程状态。
因此,现今 Java 中线程的本质,其实就是操作系统中的线程,其线程库和线程模型很大程度上依赖于操作系统(宿主系统)的具体实现,比如在 Windows 中 Java 就是基于 Wind32 线程库来管理线程,且 Windows 采用的是一对一的线程模型。
3.2 Java线程创建数量
每个线程都有一个线程栈空间通过-Xss设置,可以通过JVM配置,JVM的默认栈大小

不考虑系统限制,可以通过如下公式计算,得出最大线程数量
线程数量=(机器本身可用内存-JVM分配的堆内存)/Xss的值
根据计算公式,得出如下结论:
结论1:JVM堆越大,系统创建的线程数量越小。
结论2:当-Xss的值越小,可生成线程数量越多。
假如我们的容器内存大小是8G,堆大小是4096M,走-Xss默认值,可以得出 最大线程数量:4096个。
我们知道操作系统分配给每个进程的内存大小是有限制的,比如32位的Windows是2G。因此操作系统对一个进程下的线程数量是有限制的,不能无限的增多。
如果考虑系统限制,主要跟以下几个参数有关系
/proc/sys/kernel/pid_max 增大,线程数量增大,pid_max有最高值,超过之后不再改变,而且32,64位也不一样
/proc/sys/kernel/thread-max 系统可以生成最大线程数量
线程是非常宝贵的资源,我们要严格控制线程的数量
『引用』:
面试官:Java线程可以无限创建吗?的更多相关文章
- 面试官问线程安全的List,看完再也不怕了!
最近在Java技术栈知识星球里面有球友问到了线程安全的 List: 扫码查看答案或加入知识星球 栈长在之前的文章<出场率比较高的一道多线程安全面试题>里面讲过 ArrayList 的不安全 ...
- 面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)
前言 这是一个真实的面试题. 前几天一个朋友在群里分享了他刚刚面试候选者时问的问题:"线程池如何按照core.max.queue的执行循序去执行?". 我们都知道线程池中代码执行顺 ...
- Java线程池的创建详解
本篇文章主要总结了Java创建线程池的三种方式以及线程池参数的详细说明,对线程池感兴趣的同学可以作为参考学习. 1)通过工具类java.util.concurrent.Executors的静态方法来创 ...
- JAVA线程池的创建与使用
为什么要用线程池? 我们都知道,每一次创建一个线程,JVM后面的工作包括:为线程建立虚拟机栈.本地方法栈.程序计数器的内存空间(下图可看出),所以线程过多容易导致内存空间溢出.同时,当频繁的创建和销毁 ...
- java线程池及创建多少线程合适
java线程池 1.以下是ThreadPoolExecutor参数完备构造方法: public ThreadPoolExecutor(int corePoolSize,int maximumPoolS ...
- java线程基础巩固---创建并启动线程
对于java的并发编程方面的东东,不管是面试还是实际工作开发都是非常重要的,而往往只要涉及到并发相关的东东有点让人觉得有点难,而实际工作中涉及到并发可能就是简单的用下同步块.上锁之类的一些简单的操作, ...
- java线程池的创建使用
利用java的多线程编程可以大大的提高系统的并发运行效率,线程越多并发执行的任务就越多,但是并不意味着效率会一直提高,相反会得到适得其反的效果. java中的多线程编程一共有三种方法: 继承Threa ...
- java线程一之创建线程、线程池以及多线程运行时间统计
线程和进程的基本概念 进程和线程是动态的概念. 进程是 "执行中的程序",是一个动词,而程序是一个名词,进程运行中程序的"代码",而且还有自己的 ...
- java线程——三种创建线程的方式
前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...
- 死磕 java线程系列之创建线程的8种方式
(手机横屏看源码更方便) 问题 (1)创建线程有哪几种方式? (2)它们分别有什么运用场景? 简介 创建线程,是多线程编程中最基本的操作,彤哥总结了一下,大概有8种创建线程的方式,你知道吗? 继承Th ...
随机推荐
- Elasticsdump 数据导入/导出
目录 一.安装过程 安装NODE 通过npm安装elasticdump 二.数据导出 实操一 实操二 实操三 三.文件导入 一.安装过程 当前工具主要是用来对ES中的数据进行数据导入/导出,以及对数据 ...
- ASP.NET Core的全局拦截器(在页面回发时,如果判断当前请求不合法,不执行OnPost处理器)
ASP.NET Core RazorPages中,我们可以在页面模型基类中重载OnPageHandlerExecuting方法. 下面的例子中,BaseModel继承自 PageModel,是所有页面 ...
- Selenium4自动化测试6--控件获取数据--下拉框级联选择、checkbox选择、时间选择器
4-下拉框级联选择 import time from selenium.webdriver.support.select import Select #pip install selenium fro ...
- java学习之旅(day.21)
HTML 初识HTML HTML: Hyper Text Markup Language(超文本标记语言) 超文本包括文字.图片.音频.视频.动画等 W3C标准 W3C :World Wide Web ...
- WPF没修改代码出现InitializeComponent报错的解决办法
问题不在于我们做错了什么,之前还好好的,我们什么都没做,就报错了.这完全不是我们的问题. 我的建议是,直接做绝一点,删除obj和debug文件夹,让他自己重新生成一次
- 为什么不推荐在Spring Boot中使用@Value加载配置
@Value注解相信很多Spring Boot的开发者都已经有接触了,通过使用该注解,我们可以快速的把配置信息加载到Spring的Bean中. 比如下面这样,就可以轻松的把配置文件中key为com.d ...
- C# winfrom 局域网版多人成语接龙(一)
在学习 springjdbc+c3p0时做了一个数据库版的获得给定词汇的成语接龙,这个做了之后,我突然就想做一个可供多人游戏的成语接龙游戏,由于自己根本不熟悉java的图形界面开发,感觉没有winfo ...
- 一个 .NET 开源的地图组件库 - Mapsui
前言 今天大姚给大家分享一个.NET开源(MIT License).免费.同时支持多平台框架(MAUI.WPF.Avalonia.Uno.Blazor.WinUI.Eto..NET Android 和 ...
- 云原生时代的"应用级"多云管理
作者:张齐 当前云计算有多种形态公有云.私有云.边缘云.虚拟机等,如何高效管理多云是当前面临的问题,在云原生时代,又该如何利用云原生技术实现多云管理?本文将讲解通过 Rainbond实现"应 ...
- Java 集合的概念
目录 集合 单列集合(Collection) Collection中的一些方法 public static < T > boolean addAll(Collection<? sup ...