【Java 并发编程】一文读懂线程、协程、守护线程

一文读懂线程、协程、守护线程

  • 1. 线程的调度
    • 1.1 协同式线程调度
    • 1.2 抢占式线程调度
    • 1.3 设置线程的优先级
  • 2. 线程的实现模型和协程
    • 2.1 内核线程实现
    • 2.2 用户线程实现
    • 2.3 混合实现
    • 2.4 Java 线程的实现
    • 2.5 协程
      • 2.5.1 出现的原因
      • 2.5.2 什么是协程
      • 2.5.3 Java19 虚拟线程 - 协程的复苏
  • 3. 守护线程(后台线程)

1. 线程的调度

在 Java 线程的生命周期一文中提到了就绪状态的线程在获得 CPU 时间片后变为运行中状态,否则就会在可运行状态或者阻塞状态,那么系统是如何分配线程时间片以及实现线程的调度的呢?下面我们就来讲讲线程的调度策略。

线程调度是指系统为线程分配 CPU 执行时间片 的过程,主要调度方式有两种:协同式线程调度(Cooperative Threads-Scheduling)抢占式线程调度(Preemptive Threads-Scheduling)

1.1 协同式线程调度

使用协同式线程调度的多线程系统,线程的执行时间由线程本身来控制,某一线程执行完毕之后,会主动通知系统切换到另外一个线程上执行。

使用协同式线程调度的最大好处就是实现简单,而且由于线程获取执行时间和切换由自己控制,切换操作对线程自己是可知的,所以没有线程同步的问题

当然,缺点也很明显:如果一个线程出了问题,则程序就会一直阻塞!一直不通知系统进行线程切换,进程一直不让出 CPU 执行时间,严重时可能导致整个系统崩溃。

1.2 抢占式线程调度

使用抢占式线程调度的多线程系统,每个线程的执行时间和是否切换由系统决定。这种实现线程调度的方式,线程的执行时间是不可控的(由系统控制),所以不会有 「一个线程导致整个进程阻塞」 的问题出现。

Java 的线程调度就是抢占式线程调度。为什么 Java 线程调度是抢占式调度?这个我们在后面的线程的实现模型分析。

1.3 设置线程的优先级

在 Java 中,Thread.yield() 可以让出 CPU 执行时间,但是对于获取执行时间,线程本身是没有办法的。对于获取 CPU 执行时间,线程唯一可以使用的手段是设置线程优先级,Java 通过一个整型成员变量 priority 来控制优先级,优先级的范围从 1~10,在线程构建的时候可以通过 setPriority(int) 方法来修改优先级,默认优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

2. 线程的实现模型和协程

为什么 Java 线程调度是抢占式调度?这需要我们了解 Java 中线程的实现模式。

我们已经知道线程其实是操作系统层面的实体,Java 中的线程怎么和操作系统层面对应起来呢?

任何语言实现线程主要有三种方式:使用内核线程实现(1:1 实现),使用用户线程实现(1:N 实现),使用用户线程加轻量级进程混合实现(N:M 实现)。

2.1 内核线程实现

内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。这种线程由操作系统内核(Kernel, 下称内核) 来完成线程切换, 内核通过操纵调度器(Scheduler) 对线程进行调度, 并负责将多个线程的任务映射到各个 CPU 上去执行。每个线程由内核调度器独立的调度,所以如果一个线程阻塞则不影响其他的线程。

程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口 - 轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间 1:1 的关系称为一对一的线程模型。

在这里插入图片描述
优点:在多核处理器的硬件的支持下,内核空间线程模型支持了真正的并行,当一个线程被阻塞后,允许另一个线程继续执行,所以并发能力较强。

缺点:每创建一个用户级线程都需要创建一个内核级线程与其对应,这样创建线程的开销比较大,会影响到应用程序的性能。

2.2 用户线程实现

从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。

使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如 “阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上” 这类问题解决起来将会异常困难,甚至不可能完成。

因而使用用户线程实现的程序一般都比较复杂,此处所讲的 “复杂” 与 “程序自己完成线程操作”,并不限制程序中必须编写了复杂的实现用户线程的代码,使用用户线程的程序,很多都依赖特定的线程库来完成基本的线程操作,这些复杂性都封装在线程库之中,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby 等语言都曾经使用过用户线程,最终又都放弃使用它。

在这里插入图片描述

2.3 混合实现

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程

用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。

在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为 N:M 的关系。许多 UNIX 系列的操作系统,如 Solaris、HP-UX 等都提供了 N:M 的线程模型实现。

在这里插入图片描述

2.4 Java 线程的实现

Java 线程在早期的 Classic 虚拟机上(JDK 1.2 以前),是用户线程实现的,但从 JDK 1.3 起, 主流商用 Java 虚拟机的线程模型普遍都被替换为基于操作系统原生线程模型来实现,即采用 1: 1 的线程模型。

以 HotSpot 为例,它的每一个 Java 线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构, 所以 HotSpot 自己是不会去干涉线程调度的,全权交给底下的操作系统去处理。

所以,这就是我们说 Java 线程调度是抢占式调度的原因。而且 Java 中的线程优先级是通过映射到操作系统的原生线程上实现的,所以线程的调度最终取决于操作系统,操作系统中线程的优先级有时并不能和 Java 中的一一对应,所以 Java 优先级并不是特别靠谱。

2.5 协程

2.5.1 出现的原因

1:1 的内核线程模型是如今 Java 虚拟机线程实现的主流选择。内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要来自于响应中断、保护和恢复执行现场的成本。系统能容纳的线程数量也很有限。

随着互联网行业的发展,微服务架构的兴起,以前处理一个请求可以允许花费很长时间在单体应用中,具有这种线程切换的成本也是无伤大雅的。 但现在在每个请求本身的执行时间变得很短、服务数量变得很多的前提下,用户本身的业务线程切换的开销甚至可能会接近用于计算本身的开销,这就会造成严重的浪费。

这样的话,对 Java 语言来说,用户线程的重新引入成为了解决上述问题一个非常可行的方案。

2.5.2 什么是协程

为什么用户线程又被称为协程呢?我们知道,内核线程的切换开销是来自于保护和恢复现场的成本, 那如果改为采用用户线程, 这部分开销就能够省略掉吗? 答案还是“不能”。 但是,一旦把保护、恢复现场及调度的工作从操作系统交到程序员手上,则可以通过很多手段来缩减这些开销。

由于最初多数的用户线程是被设计成协同式调度(Cooperative Scheduling)的,所以它有了一个别名 - “协程”(Coroutine) 完整地做调用栈的保护、恢复工作,所以今天也被称为“有栈协程”(Stackfull Coroutine)。

协程的主要优势是轻量, 无论是有栈协程还是无栈协程, 都要比传统内核线程要轻量得多。如果进行量化的话, 那么如果不显式设置,则在 64 位 Linux 上 HotSpot 的线程栈容量默认是 1MB,此外内核数据结构(Kernel Data Structures)还会额外消耗 16KB 内存。与之相对的, 一个协程的栈通常在几百个字节到几 KB 之间, 所以 Java 虚拟机里线程池容量达到两百就已经不算小了, 而很多支持协程的应用中, 同时并存的协程数量可数以十万计。

协程当然也有它的局限, 需要在应用层面实现的内容(调用栈、 调度器这些)特别多,同时因为协程基本上是协同式调度,则协同式调度的缺点自然在协程上也存在。

总的来说,协程机制适用于被阻塞的,且需要大量并发的场景(网络 IO),不适合大量计算的场景,因为协程提供规模(更高的吞吐量),而不是速度(更低的延迟)。

2.5.3 Java19 虚拟线程 - 协程的复苏

2022-09-20,JDK 19 发布了 GA 版本,备受瞩目的协程功能也算尘埃落地,不过,此次 GA 版本并不没有以协程来命名,而是使用了 VirtualThread(虚拟线程),并且还是 preview 预览版本。

在 JDK 19 源码中,官方直接在 java.lang 包下新增一个 VirtualThread 类来表示虚拟线程,和现有的 Thread类并驾齐驱,为了更好的区分虚拟线程和原有Thread 线程,官方又给 Thread 类赋予了一个高大上的名字:平台线程。

大家有想具体学习的可以参考:Java终于发布了"协程"–虚拟线程,原来上手这么简单!

3. 守护线程(后台线程)

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的时候,Java 虚拟机将会退出。可以通过调 Thread.setDaemon(true) 将线程设置为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。

Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Activiti7与Spring、Spring Boot整合开发

Activiti整合Spring 一、Activiti与Spring整合开发 1.1 Activiti与Spring整合的配置 1)、在pom.xml文件引入坐标 如下 <properties><slf4j.version>1.6.6</slf4j.version><log4j.version>1.2.12</log4j.version> </properties> <d…

【源码教程案例】AI绘画与安全在未来主要方向有哪些?

AI绘画在未来有许多潜在的发展方向,以下是一些可能的重点领域 高质量图像生成:随着生成模型的不断改进,未来的AI绘画可能会产生更高质量、更真实的图像,以满足各种应用场景的需求。 个性化创作:AI绘画可以通过用户的个性化偏好和需求来定制艺术作品。这种定制可能包括颜…

【gitlab部署】centos8安装gitlab(搭建属于自己的代码服务器)

这里写目录标题部署篇序言要求检查系统是否安装OpenSSH防火墙问题准备gitlab.rb 配置坑点一忘记root密码重置使用篇gitlab转换成中文git关闭注册入口创建用户部署篇 序言 在团队开发过程中&#xff0c;想要拥有高效的开发效率&#xff0c;选择一个好的代码开发工具是必不可少的…

JUnit5用户手册~并行执行

两种运行模式 SAME_THREAD&#xff1a;默认的&#xff0c;测试方法在同一个线程CONCURRENT&#xff1a;并行执行&#xff0c;除非有资源锁junit-platform.properties配置参数配置所有测试方法都并行 junit.jupiter.execution.parallel.enabled true junit.jupiter.execution.…

Kafka3.0.0版本——生产者分区及分区策略

目录一、生产者分区优点二、生产者发送消息的分区策略2.1、默认的分区器2.2、指定分区(partition)值2.3、没有指明分区(partition)值&#xff0c;但有key 的情况2.4、 既没有分区(partition)值&#xff0c;又没有key 值的情况三、指定分区(partition)值的代码示例四、没有指明分…

vscode折叠展开快捷键

1.折叠所有代码 (按住ctrl 分别点击k和0) ctrlk,ctrl0 2.展开所有代码 (按住ctrl 分别点击k和j) ctrlk,ctrlj 3. 折叠鼠标竖线所在位置的节点以及当前节点下的子节点&#xff08;递归折叠&#xff09; ctrlk,ctrl[ 4. 展开鼠标竖线所在位置的节点以及当前节点下的子节点&#…

OpenFeign 源码解读:动态代理+负载均衡实现

OpenFeign使用EnableFeignClients开启服务&#xff0c;该注解标有Import(FeignClientsRegistrar.class)&#xff0c;该ImportBeanDefinitionRegistrar会利用扫描路径的方式扫描java文件中带有的FeignClient(...)的接口&#xff0c;关于这种扫描注解的方式&#xff0c;我仿照写了…

软件测试 - 测试用例常见面试题

1.测试用例的要素测试用例是为了实施测试而向被测试的系统提供的一组集合, 这组集合包含 : 测试环境, 操作步骤, 测试数据, 预期结果等要素.例如 : 在 B 站输入框输入一个空格, 检查结果测试用例标题 : 输入框输入空格测试环境 : Windows 系统, 谷歌浏览器-版本 111.0.5563.65&…

固态硬盘需要分区吗 固态硬盘怎么分区

磁盘分区是在磁盘中划分几个逻辑部分&#xff0c;来更充分的利用磁盘空间&#xff0c;对保存的数据进行分类储存&#xff0c;方便使用。今天小编给大家介绍一下&#xff0c;固态硬盘需要分区吗&#xff0c;固态硬盘怎么分区。 一、固态硬盘需要分区吗 固态硬盘是需要分区的&a…

Redis:redis通用命令;redis常见数据结构;redis客户端;redis的序列化

一、redis命令 1.redis通用命令 Redis 通用命令是一些 Redis 下可以作用在常用数据结构上的常用命令和一些基础的命令 常见的命令有&#xff1a; keys 查看符合模板的所有key&#xff0c;不建议在生产环境设备上使用&#xff0c;因为keys会模式匹配所有符合条件的key&#…

js常见的9种报错记录一下

js常见报错语法错误(SyntaxError)类型错误(TypeError)引用错误(ReferenceError)范围错误(RangeError)运行时错误(RuntimeError)网络错误&#xff08;NetworkError&#xff09;内部错误&#xff08;InternalError&#xff09;URI错误&#xff08;URIError&#xff09;eval错误&a…

electron+vue3全家桶+vite项目搭建【五】集成Pinia全局状态管理

文章目录引入1.引入依赖2.集成Pinia3.使用pinia4.测试效果引入 在vue2的体系中&#xff0c;vuex是官方推荐的状态管理工具&#xff0c;而vue3的体系中&#xff0c;官网同样推荐了一款状态管理工具&#xff0c;他就是 Pinia Pinia官网 demo项目地址 1.引入依赖 npm install…

docker 安装运行 nacos2.0.3

目录 1、拉取镜像 2、挂载目录 mkdir -p /opt/nacos/logs/ #新建logs目录mkdir -p /opt/nacos/conf/ #新建配置目录vim /opt/nacos/conf/application.properties #修改配置文件 3、application.properties内容 4、初始化nacos的脚…

Vue的简单介绍

一、简介 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;…

生成式 AI 背后的共同框架:Stable Diffusion、DALL-E、Imagen

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 框架 这些生成式 AI 的整体功能为&#xff1a;输入「文字」&#xff0c;返回「图像」&#xff0c;即 Text-to-image Gener…

DBeaver安装教程及基础使用手册

目录 一、简介 基本特性 二、DBeaver安装 三、连接SQL方法 一、简介 DBeaver是免费和开源&#xff08;GPL&#xff09;为开发人员和数据库管理员通用数据库工具。 它支持任何具有一个JDBC驱动程序数据库&#xff0c;也可以处理任何的外部数据源。 DBeaver 通过 JD…

自动化运维软件ansible

一、ansible 基于python语言。简单快捷&#xff0c;被管理端不需要启服务。直接走ssh协议,需要验证&#xff0c;所以机器多的话速度会较慢。 1、ansible环境搭建 5.确认和配置yum源(需要epel源) 免密登录复制的时候可以直接 写ip 不加参数-i 2、服务器分组&#xff08;主机清单…

java的Lambda表达式与方法引用详解

1. 定义 Lambda 表达式&#xff0c;也可称为闭包&#xff0c;它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数&#xff08;函数作为参数传递进方法中&#xff09;。 使用 Lambda 表达式可以使代码变的更加简洁紧凑。 1.1 通用定义 lambda 表达…

知识图谱实战应用4-知识图谱中寻找相似用户(协同过滤算法)

大家好&#xff0c;我是微学AI&#xff0c;今天给大家讲一下知识图谱中利用协同过滤算法寻找相似用户。大家会看到一个新的名词&#xff1a;“协同过滤”&#xff0c;下面来介绍一下协同过滤算法。 一、协同过滤算法 协同过滤算法是一种基于用户行为分析的推荐算法。它的基本…

php微信小程序java+Vue高校课程课后辅导在线教育系统nodejs+python

目 录 1绪论 1 1.1项目研究的背景 1 1.2开发意义 1 1.3项目研究现状及内容 5 1.4论文结构 5 2开发技术介绍 7 2.1 B/S架构 7 2.2 MySQL 介绍 7 2.3 MySQL环境配置 7 2.5微信小程序技术 8 3系统分析 9 3.1可行性分析 9 3.1.1技术可行性 9 3.1.2经济可行性 9 3.1.3操作可行性 10 …