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

java · 浏览次数 : 27

小编点评

操作系统的线程和Java的线程是有关联的,但它们并不完全相同。操作系统线程是操作系统层面的概念,而Java线程是Java语言层面的概念。 操作系统线程可以无限创建,因为操作系统可以调度线程所属的进程,而Java线程则是在用户空间中实现的,其调度是由操作系统内核完成的。 操作系统区分内核态和用户态是为了保护计算机系统的安全性和稳定性。内核态运行的进程可以访问系统的任何资源,包括内存、设备、驱动程序等,不受限制。而用户态运行的进程只能访问用户程序的数据,拥有较低的权限。 Java线程模型主要有三种:多对一线程模型、一对一线程模型和多对多线程模型。不同的线程模型有不同的优缺点,需要根据实际需求选择合适的模型。 Java线程创建数量受到操作系统限制,因为操作系统分配给每个进程的内存大小是有限的。可以通过调整JVM的堆大小和-Xss参数来控制线程数量。 总之,操作系统线程和Java线程虽然有关联,但它们并不完全相同。在实际应用中,需要根据操作系统和应用程序的需求来选择合适的线程模型和线程数量。

正文

Java线程无限创建

哈喽,大家好🎉,我是世杰。

⏩本次给大家介绍一下操作系统线程和Java的线程以及二者的关联

1. 面试连环call

  1. Java线程可以无限创建吗?
  2. Java线程和操作系统线程有什么关联?
  3. 操作系统为什么要区分内核态和用户态?

⏩要想解答这些问题,我们要先从操作系统线程开始说起,让我们开始吧🎉🎉🎉


2. 操作系统线程

2.1 内核态和用户态

根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

  • 用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限

  • 内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。

usermode-and-kernelmode

那为什么要区分用户态和内核态呢?

  • 在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配设置时钟IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令

  • 如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。

2.2 用户态线程

早期的操作系统中,所有的线程都是在用户态下实现,操作系统只能调度线程所属的进程,而无法调度线程

在这种模型下,用户需要自己定义线程的数据结构、创建、销毁、调度和维护等,这些线程运行在某个进程内,操作系统直接对进程进行调度

img

『优点』

  • 即使操作系统原生不支持线程,我们也可以通过库函数来支持线程
  • 线程的调度只发生在用户态,避免了操作系统从内核态到用户态的转换开销。

『缺点』

  • 由于操作系统无法调度线程,CPU 的时间片切换是以进程为维度的,如果进程中某个线程进行了耗时比较长的操作,那么由于用户态中没有时钟中断机制,就会导致此进程中的其它线程因为得不到 CPU 资源而长时间的持续等待;
  • 如果某个线程进行系统调用时比如缺页中断而导致了线程阻塞,此时操作系统也会阻塞整个进程,即使这个进程中其它线程还在工作。

2.3 内核态线程

现代操作系统,包括 Windows、Linux、Mac OS X 和 Solaris 等,都支持内核线程。线程运行在内核空间,直接由内核负责,由内核来完成调度。

此时我们可以直接使用操作系统中已经内置好的线程,线程的创建、销毁、调度和维护等,都直接由操作系统的内核来实现,我们只需要使用系统调用就好了,不需要像用户级线程那样自己设计线程调度。

img

内核线程和用户线程的对应关系并不完全是1对1,其关联模式有三种

2.4 线程模型

多对一线程模型

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

img

『优点』

  • 用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换。使线程的创建、调度、同步等非常快。

『缺点』

  • 由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行
  • 内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的优先级调度等操作

一对一线程模型

该模型为每个用户态的线程分配一个单独的内核态线程,在这种情况下,每个用户态都需要通过系统调用创建一个绑定的内核线程。 这种模型允许所有线程并行执行,能够充分利用多核优势。目前 Linux 中的线程OpenJDK Java 线程等采用的都是一对一线程模型。每一个JVM线程,都有一个对应的内核线程。

img

『优点』

  • 解决了多对一模型的阻塞调度问题
  • 实现起来较为简单

『缺点』

  • 每创建一个用户线程,相应地就需要创建一个内核线程,开销较大,因此需要限制整个系统的线程数量
  • 对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换

多对多线程模型

这种模式下会为 n 个用户态线程分配 m 个内核态线程。m 通常小于 n。一种可行的策略是将 m 设置为核数。这种多对多的关系,减少了内核线程,同时也保证了多核心并行。多对多模型中线程的调度需要由内核态和用户态一起来实现,例如线程间同步需要用户态和内核态共同实现。用户态和内核态的分工合作导致实现该模型非常复杂。

PS: Linux多线程模型曾经也想使用该模型,但它太复杂,要对内核进行大范围改动,所以还是采用了一对一的模型

img

『优点』

  • 多对多模型将任意数量的用户线程复用到相同或更少数量的内核线程上,结合了一对一和多对一模型的最佳特性
  • 用户对创建的线程数没有限制

『缺点』

  • 实现起来非常复杂

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的默认栈大小

img

不考虑系统限制,可以通过如下公式计算,得出最大线程数量

线程数量=(机器本身可用内存-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 系统可以生成最大线程数量

线程是非常宝贵的资源,我们要严格控制线程的数量


『引用』:

Threads

Java 线程和操作系统的线程有啥区别?

一台 Java 服务器可以跑多少个线程?

操作系统常见面试题总结(上)

用户态线程和内核态线程的区别

与面试官:Java线程可以无限创建吗?相似的内容:

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

哈喽,大家好,我是世杰。 ⏩本次给大家介绍一下操作系统线程和Java的线程以及二者的关联 1. 面试连环call Java线程可以无限创建吗? Java线程和操作系统线程有什么关联? 操作系统为什么要区分内核态和用户态? ⏩要想解答这些问题,我们要先从操作系统线程开始说起,让我们开始吧�

Java多线程-JUC-1(八)

前面把线程相关的生命周期、关键字、线程池(ThreadPool)、ThreadLocal、CAS、锁和AQS都讲完了,现在就剩下怎么来用多线程了。而要想用好多线程,其实是可以取一些巧的,比如JUC(好多面试官喜欢问的JUC,就是现在要讲的JUC)。JUC就是java.util.concurrent的

Java面试题:线程池内“闹情绪”的线程,怎么办?

在Java中,线程池中工作线程出现异常的时候,默认会把异常往外抛,同时这个工作线程会因为异常而销毁,我们需要自己去处理对应的异常,异常处理的方法有几种:在传递的任务中去处理异常,对于每个提交到线程池中的执行的任务,可以提前通过异常进行捕获,这样即便出现了异常,也不会影响线程池中的工作线程,使用Fut...

Java并发篇:6个必备的Java并发面试种子题目

免费体验AI绘画:https://www.topgpt.one;文章涉及了几个常见的并发编程相关的主题。首先,线程的创建和生命周期是面试中常被问及的话题,面试官可能会询问如何创建线程、线程的状态转换以及如何控制线程的执行顺序等。其次,synchronized关键字是用于实现线程同步的重要工具,面试中可能会涉及到它的使用场景以及与其他同步机制的比较。此外,抽象队列同步器(AQS)是Java并发编程中

Java面试题:SimpleDateFormat是线程安全的吗?使用时应该注意什么?

在Java开发中,我们经常需要获取和处理时间,这需要使用到各种不同的方法。其中,使用SimpleDateFormat类来格式化时间是一种常见的方法。虽然这个类看上去功能比较简单,但是如果使用不当,也可能会引发一些问题。

Java面试题:Spring Bean线程安全?别担心,只要你不写并发代码就好了!

Spring Bean是单例模式,即在整个应用程序上下文中只有一个实例。在多线程环境下,Singleton Scope Bean可能会发生线程安全问题。Spring Bean是否线程安全取决于Bean的作用域和Bean本身的实现。在使用Singleton Scope Bean时需要特别注意线程安全问...

面试官:核心线程数为0时,线程池如何执行?

线程池是 Java 中用于提升程序执行效率的主要手段,也是并发编程中的核心实现技术,并且它也被广泛的应用在日常项目的开发之中。那问题来了,如果把线程池中的核心线程数设置为 0 时,线程池是如何执行的? 要回答这个问题,我们首先要了解在正常情况下,线程池的执行流程,也就是说当有一个任务来了之后,线程池

Java面试题:如果你这样做,你会后悔的,两次启动同一个线程~~~

当一个线程被启动后,如果再次调start()方法,将会抛出IllegalThreadStateException异常。 这是因为Java线程的生命周期只有一次。调用start()方法会导致系统在新线程中运行执行体,但是如果线程已经结束,则不能再次使用,需要重新创建一个新的线程对象并调用start()...

万字长文详解Java线程池面试题

大家好,我是王有志。今天是《面霸的自我修养》第 6 篇文章,我们一起来看看面试中会问到哪些关于线程池的问题吧。

你真的了解Java内存模型JMM吗?

面试连环call: 1. 什么是Java内存模型(JMM)? 为什么需要JMM? 2. Java线程的工作内存和主内存各自的作用? 3. Java缓存一致性问题? 4. Java的并发编程问题?