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

news2024/9/24 17:12:54

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

  • 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 块中的内容来确保执行关闭或清理资源的逻辑

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cat原理简析

Cat原理简析 链路追踪系统设计思路如何高效组织业务日志如何动态串联业务日志通用解决方案链路定义链路染色链路上报链路存储 Cat原理客户端原理API设计序列化和通信客户端埋点核心类分析流程分析启动流程:消息生产Context 线程本地变量Transaction事务的开启其他类型消息组合关…

网页设计方向有哪些SCI期刊推荐? - 易智编译EaseEditing

网页设计和开发方向主要涉及人机交互、用户体验、可访问性等方面,以下是几个相关的SCI期刊推荐: ACM Transactions on Computer-Human Interaction (ACM TOCHI): 该期刊由ACM(Association for Computing Machinery)出…

一次etcd变更引发的惨案

问题描述 在做etcd的数据变更时候,etcd在组成集群的时候出现leader不断切换问题,导致集群不稳定,都面将不健康的etcd节点踢出,只剩etcd单节点,后面将踢出的etcd节点重新加入现有etcd,导致etcd集群奔溃&…

【java踩坑搞起】MybatisPlus封装的mapper不支持 join,那咋办

众所周知,Mybatis Plus 封装的 mapper 不支持 join,如果需要支持就必须自己去实现。但是对于大部分的业务场景来说,都需要多表 join,要不然就没必要采用关系型数据库了。 直到前几天,偶然碰到了这么一款叫做mybatis-p…

权限提升:网站后台.(提权思路.)

权限提升:网站后台 权限提升简称提权,由于操作系统都是多用户操作系统,用户之间都有权限控制,比如通过 Web 漏洞拿到的是 Web 进程的权限,往往 Web 服务都是以一个权限很低的账号启动的,因此通过 Webshell …

Form Designer V2发布

基于Ant Design 和 jQuery UI 的表单设计器 github 地址 特性 React Vue 3.x Typescript 统一的组件定义,对Vue 和React 的实现提供一个统一的组件定义描述 概念 Component 组件Layout 布局,一种特殊的ComponentComponent Editor 组件属性编辑器Comp…

【开发日志】2023.04 ZENO----Composite----CompImport、ReadImageFlie

CompImport TEST: 用ParticlesWrangle创造属性A(紫色),B(青色) ,用CompImport结点将属性转化为图片输入到Composite3进行合成 Input: Output: /* 导入地形网格的属性,可能会有多个属…

Docker安装 docker-registry 镜像仓库

一、运行如下命令安装docker-registry镜像仓库: docker run -d \ -p 5000:5000 \ -v /usr/local/registry:/var/lib/registry \ --restartalways \ --name registry \ registry:2 二、测试镜像生成并推送到镜像仓库 1、新建一个目录 mkdir target 2、上传一…

Taro+Vue3 小程序引入echarts表

背景:根据需求在一个报告界面需要展示不同的echarts表来使数据更友好的显示。 效果如下: 一.taro支持echarts 官方说明:Taro 文档支持引用小程序端第三方组件库 物料文档:Taro 物料市场 | 让每一个轮子产生价值 二.引入echart…

Qt5.12實戰之Linux靜態庫編譯與調用完整過程

1.安裝gedit sudo apt-get install gedit -y 2.使用gedit編輯靜態庫源文件test.cpp gedit test.cpp 輸入下面內容 &#xff1a; #include <stdio.h> int func() {return 888; } 如下圖操作&#xff1a; 保存test.cpp並編譯 爲目標文件 gcc -c test.cpp如下圖示&am…

【剑指 Offer】(1)

文章目录前言一、 数组中重复的数字:fire: 解决方法:dog: 代码二、二维数组中的查找:fire:思路:dog:代码三、替换空格:fire:思路:dog: 代码四、从尾到头打印链表:fire:思路:dog:代码:dog: 代码五、重建二叉树:fire:思路:dog: 代码总结前言 剑指offer系列是一本非常著名的面试题…

【BUG SHOW】一个由高并发引起的缺陷分析

软件质量保障: 所寫即所思&#xff5c;一个阿里质量人对测试的所感所悟。 缺陷介绍 平台有这样的两个功能&#xff1a; ​功能01: 用户发起支付&#xff0c;成功则将表pay单据状态推进SUCCESS&#xff0c;然后发出支付结果消息&#xff1b;反之如果支付失败&#xff0c;则状态…

vue 报错 error:03000086:digital envelope routines::initialization error解决方案

目录 1. 引言: 2. 更换版本出现问题: 3. 出现原因: 4. 解决办法: -> 4. 1 删了 再换回16.15版本 -> 4.2 指令修改(好使) ---> 4.2.1效果如图 -> 4.3 其他指令就别试了 压根不好使 1. 引言: npm出现问题 , 卸载后 装了个新node 18.15版本 2. 更换版本…

Servlet-搭建个人博客系统(MVC架构模式简介,maven的配置和使用)

目录 1. MVC架构模式简介 2. maven的配置和使用 3. 项目总述&#x1f43b; 3.1 &#x1f34e;Controller层 3.2 &#x1f34e;Model层 3.3 &#x1f34e;View层 4. 页面的主要功能实现&#x1f43b; 4.1 &#x1f34e;登陆页面&#xff08;login.html&#xff09; 4.2…

Oracle Recovery Tools快速恢复断电引起的无法正常启动数据库----惜分飞

由于异常断电,数据库启动报错ORA-01113和ORA-01110&#xff0c;ORA-00322和ORA-00312以及ORA-00314和ORA-00312错误 Mon Apr 17 09:35:04 2023 ALTER DATABASE OPEN Errors in file D:\APP\ADMINISTRATOR\diag\rdbms\orcl\orcl\trace\orcl_ora_10192.trc: ORA-01113: 文件 1 需…

史上最牛二分查找,不服来战

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《算法详解》&#xff0c;笔者用重金(时间和精力)打造&#xff0c;将算法知识一网打尽&#xff0c;希望可以…

音视频-ffplay的音视频同步

最近自己在做一个视频播放器&#xff0c;渲染视频帧时有些疑惑&#xff0c;所以特意来学习一下ffplay中是如何处理视频帧的渲染的&#xff01;&#x1f60a; 我自己最初的理解是这样 1、只关心视频本身的时间戳&#xff0c;不考虑音视频同步以及其他的同步时钟&#xff0c;或…

写了那么久的文章,现在才改回来!

大家好&#xff0c;我是即兴小索奇&#xff0c;最近在阅读文章时发现了自己文章的一个缺陷&#xff0c;就记录下来并分享给大家&#xff0c;大家写文章时也可以借鉴。 这是我以前写的的文章&#xff0c;light亮色下显示正常 -当我不经意间把手机调成深色模式阅读文章时&#xf…

Leetcode912.排序数组(三路划分)

文章目录 一、三路划分二、Leetcode912.排序数组 一、三路划分 为何还会有三路划分&#xff1f; 快速排序算法在某个数据大量重复时效率极低&#xff0c;在运行程序时会超出时间限制&#xff0c;为了解决数据大量重复的情况下&#xff0c;三路划分诞生了。三路划分是基于快速排…

第五回:如何使用ListView Widget

文章目录概念介绍使用方法示例代码我们在上一章回中介绍了Container Widget,本章回中将介绍 ListView这种Widget&#xff0c;闲话休提&#xff0c;让我们一起Talk Flutter吧。概念介绍 ListView就是一个滚动的列表&#xff0c;它可以看作是在Column的基础上添加了滚动功能&…