Java线程协程

article/2025/11/6 11:41:29

目录

线程的实现(OS&&JVM)

1.内核线程实现

2.用户线程实现

3.混合实现

4.Java线程的实现 ——如何实现不受Java虚拟机规范的约束

Java线程调度——系统自动完成(可能被干预)

Java线程状态转换

内核线程的局限

协程的复苏——Java19中的虚拟线程


线程的实现(OS&&JVM)

线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度。

目前线程 是Java里面进行处理器资源调度的最基本单位。

主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作 的统一处理,每个已经调用过start()方法且还未结束的java.lang.Thread类的实例就代表着一个线程。

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

1.内核线程实现

使用内核线程实现的方式也被称为1:1实现

内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。

每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就称为多线程内核 (Multi-Threads Kernel)。

程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程

这种轻量级进程与内核线程之间1:1 的关系称为一对一的线程模型。

由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使其中某一个轻量级进程 在系统调用中被阻塞了,也不会影响整个进程继续工作。

轻量级进程也具有它的局限性

  • 首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。
  • 其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈 空间),因此一个系统支持轻量级进程的数量是有限的。 

2.用户线程实现

使用用户线程实现的方式被称为1:N实现。

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

狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存 在及如何实现的

用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。

如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的

这种进程与用户线程之 间1:N的关系称为一对多的线程模型。

用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都 需要由用户程序自己去处理。

线程的创建、销毁、切换和调度都是用户必须考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至有些是不可能实现的。

因为使用用户线程实现的程序通 常都比较复杂,除了有明确的需求外(譬如以前在不支持多线程的操作系统中的多线程程序、需要支持大规模线程数量的应用),一般的应用程序都不倾向使用用户线程。

Java、Ruby等语言都曾经使用过用户线程,最终又都放弃了使用它。

但是近年来许多新的、以高并发为卖点的编程语言又普遍支持了用户线程,譬如Golang、Erlang等,使得用户线程的使用率有所回升。

3.混合实现

一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现

在这种混合实现下,既存在用户线程,也存在轻量级进程。

用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。

而操作系统支持的轻量级进程作为用户线程和内核线程之间的桥梁, 这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,这大大降低了整个进程被完全阻塞的风险。

在这种混合模式中,用户线程与轻量级进程的数量比是不定的,是N:M的关系

4.Java线程的实现 ——如何实现不受Java虚拟机规范的约束

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

HotSpot为例,它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以HotSpot自己是不会去干涉线程调度的(可以设置线程优先级给操作系统提 供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的。


Java线程调度——系统自动完成(可能被干预)

线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式 (Cooperative Threads-Scheduling)线程调度抢占式(Preemptive Threads-Scheduling)线程调度

协同式调度

如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行 完了之后,要主动通知系统切换到另外一个线程上去。

协同式多线程的最大好处是实现简单,而且由 于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知的,所以一般没有什么线程同步的问题。

Lua语言中的“协同例程”就是这类实现。它的坏处也很明显:线程执行时间不可控制,甚至如果一个线程的代码编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。

很久以前的Windows 3.x系统就是使用协同式来实现多进程多任务的,那是相当不稳定的,只要 有一个进程坚持不让出处理器执行时间,就可能会导致整个系统崩溃。

抢占式调度

如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线 程本身来决定

譬如在Java中,有Thread::yield()方法可以主动让出执行时间(线程变成Ready态),但是如果想要主动获取执行时间,线程本身是没有什么办法的。

在这种实现线程调度的方式下,线程的执行时间是系统可控 的,也不会有一个线程导致整个进程甚至整个系统阻塞的问题。Java使用的线程调度方式就是抢占式调度。

与前面所说的Windows 3.x的例子相对,在Windows 9x/NT内核中就是使用抢占式来实现多进程 的,当一个进程出了问题,我们还可以使用任务管理器把这个进程杀掉,而不至于导致系统崩溃。

设置线程优先级——并不是一项稳定的调节手段

“建议”操作系统给某些线程多分配一 点执行时间,另外的一些线程则可以少分配一点——这项操作是通过设置线程优先级来完成的。

Java 语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY)。在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。

现代的操作系统基本都提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应,如Solaris中线程有 2147483648(2的31次幂)种优先级,但Windows中就只有七种优先级。

Java线程优先级与Windows线程优先级之间的对应关系

优先级可能会被系统自行改变,例如在Windows系统中存在一个叫“优先级推进器”的功能(Priority Boosting,当然它可以被关掉),大致作用是当系统发现一个线程被执行得特别频繁时,可能会越过线程优先级去为它分配执行时间,从而减少因为线程频繁切换而带来的性能损耗。因此,我们并不能在程序中通过优先级来完全准确判断 一组状态都为Ready的线程将会先执行哪一个。

Java线程状态转换

Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能有且只有其中的一种状态,并 且可以通过特定的方法在不同状态之间转换。

  • 新建(New):创建后尚未启动的线程处于这种状态。
  • 运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可 能正在执行,也有可能正在等待着操作系统为它分配执行时间。
  • 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显式唤醒。
    • 以下方法会让线程陷入无限期的等待状态:
      • 没有设置Timeout参数的Object::wait()方法;
      • 没有设置Timeout参数的Thread::join()方法;
      • LockSupport::park()方法。
  • 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。
    • 以下方法会让线程进入限期等待状 态:
      • Thread::sleep()方法;
      • 设置了Timeout参数的Object::wait()方法;
      • 设置了Timeout参数的Thread::join()方法;
      • LockSupport::parkNanos()方法;
      • LockSupport::parkUntil()方法。
  • 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

 内核线程的局限

现代B/S系统中一次对外部业务请求的响应,往往需要分布在不同机器上的大量服务共同协作来实现,这种服务细分的架构在减少单个服务复杂度、增加复用性的同时,也不可避免地增加了服务的数量,缩短了留给每个服务的响应时间。

这要求每一个服务都必须在极短的时间内完成计算,这样组合多个服务的总耗时才不会太长;也要求每一个服务提供者都要能同时处理数量更庞大的请求,这样才不会出现请求由于某个服务被阻塞而出现等待。

Java目前的并发编程机制就与上述架构趋势产生了一些矛盾,1:1的内核线程模型是如今Java虚拟 机线程实现的主流选择,但是这种映射到操作系统上的线程天然的缺陷是切换、调度成本高昂,系统能容纳的线程数量也很有限。

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

协程的复苏——Java19出现虚拟线程

内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要 来自于响应中断、保护和恢复执行现场的成本

假设发生了这样一次线程切换: 线程A -> 系统中断 -> 线程B

处理器要去执行线程A的程序代码时,并不是仅有代码程序就能跑得起来,程序是数据与代码的 组合体,代码执行时还必须要有上下文数据的支撑。

而这里说的“上下文”,以程序员的角度来看,是方法调用过程中的各种局部的变量与资源

以线程的角度来看,是方法的调用栈中存储的各类信息

以操作系统和硬件的角度来看,则是存储在内存、缓存和寄存器中的一个个具体数值

物理硬件的各种存储设备和寄存器是被操作系统内所有线程共享的资源,当中断发生,从线程A切换到线程B去执行之前,操作系统首先要把线程A的上下文数据妥善保管好,然后把寄存器、内存分页等恢复到线程B挂起时候的状态,这样线程B被重新激活后才能仿佛从来没有被挂起过。

这种保护和恢复现场的工作,免不了涉及一系列数据在各种寄存器、缓存中的来回拷贝,当然不可能是一种轻量级的操作。

如果说内核线程的切换开销是来自于保护和恢复现场的成本,那如果改为采用用户线程,这部分 开销就能够省略掉吗?答案是“不能”。

协程——协同式、非协同式、可自定义调度

“有栈协程”:会完整地做调用栈的保护、恢复工作。

“无栈协程”:无栈协程本质上是一种有限状态机,状态保存在闭包里,自然比有栈协程恢复调用栈要轻量得多,但功能也相对更有限。典型应用,即各种语言中的await、async、yield这类关键字。

协程的主要优势轻量,无论是有栈协程还是无栈协程,都要比传统内核线程要轻量得多。

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

协程局限:需要在应用层面实现的内容(调用栈、调度器这些)特别多


http://chatgpt.dhexx.cn/article/F6LwQ3DG.shtml

相关文章

CDH6.3.2安装文档

正式安装之前,先说明一下CDH是基于Apache Hadoop及相关项目的发行版。CDH通过WEB界面管理,并提供了hadoop的两个核心功能:可扩展存储和分布式计算,以及企业级的重要功能。CDH是遵循Apache-licensed的开源软件,提供了基…

CDH数仓项目(一) —— CDH安装部署搭建详细流程

0 说明 本文以CDH搭建数据仓库,基于三台阿里云服务器从零开始搭建CDH集群,节点配置信息如下: 节点内存安装服务角色chen10216Gcloudera-scm-serverchen1038Gcloudera-scm-agentchen1048Gcloudera-scm-agent 上传需要用到的安装包&#xff…

CDH5.8安装说明

#CDH5.8安装说明 (Hadoop) 使用过Ambari,不知道是因为没有商业运作支撑还是社区活跃度有限,总体管理能力只能算凑合。很多Hadoop组件版本都不高,Spark也才1.3.X,Sqoop还是1.4.6.2.3,相对版本都比较低,而且…

大数据CDH安装详细教程

1.环境准备 1.1 服务器配置(理想配置) 1.2 修改主机名和hosts文件(所有节点) [roothadoop001 ~]# vim /etc/hosts vim /etc/hostname1.3 关闭防火墙 systemctl stop firewalld systemctl disable firewalld1.4 SSH免密登录(主节点) ssh-keygen -t rsa #分发到所有节点 ssh…

CDH6安装

官方文档 https://www.cloudera.com/documentation/enterprise/6/6.0/topics/installation.html 安装之前 JDK兼容性在不同的Cloudera Manager和CDH版本中也有所不同。某些版本的CDH 5与JDK 7和JDK 8兼容。在这种情况下,请确保所有服务都部署在同一主要版本上。例…

Cloudera(CDH) 简介和在线安装

实验背景 笔者需要维护线上的hadoop集群环境,考虑在本地搭建一套类似的hadoop集群,便于维护与管理。 Cloudera 简介 经过搜索发现Cloudera产品很适合笔者当前需求,于是开始研究Cloudera(CDH)的安装与使用,参…

CDH6.3.1安装

CDH6.3.1安装遇到很多问题,我想主要是由于条件有限,毕竟自己的电脑内存不如专业集群的内存大(如果是内存和硬盘充足,有些是可以避免的,甚至不会出现报错的情况),这里就介绍一下我用VMware安装的…

CDH安装配置

Cloudera5.14配置 准备工作 软件下载软件安装 JDK安装 所有节点 安装环境变量配置 sudo vim /etc/profile export JAVA_HOME/usr/java/default export PATH$JAVA_HOME/bin:$PATH 使用root用户 echo "JAVA_HOME/usr/java/default" >> /etc/environment …

CDH 6.3.2 安装(一)

目录 一、CDH框架介绍 1、CDH介绍 2、CDH官方网址 3、CDH官方文档 4、CDH集群扩容 5、CDH硬件要求 6、CDH k8s服务开启 二、CDH依赖安装 1、安装通用依赖 2、网络工具安装 3、防火墙服务安装 4、进程树形工具安装 5、其它依赖安装 三、Linux系统配置 1、主机名配…

CDH安装手册(自整理)

文章目录 前言1.组件版本2.集群规划(三台服务器为例)3.配置linux静态IP4.修改hosts文件,并实现免密登录5.创建集群分发脚本6.关闭防火墙和SELINUX(所有节点)7.配置NTP时钟同步8.安装jdk和mysql9.搭建本地yum源并安装10…

安装篇2 - 安装CDH

登陆Cloudera Manager平台 http://192.168.60.100:7180 账号密码:admin/admin 1.1 1.2 1.3 免费 2.1 2.2 2.3 2.4 选择CDH和Flink 2.5 将parcel包内的Hadoop,Hive等组件分发到各个节点进行解压激活 2.6 3.1 自行选择 自定义选择Zookeeper&#xff…

CDH5(CDH 5.16.1)安装

日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) CDH 6系列(CDH 6.0.0、CDH 6.1.0、CDH 6.2.0等)安装和使用 CDH5(CDH 5.16.1)安装 linux配置 1.…

CDH 6.3.2 安装(二)

目录 一、网络配置 1、静态网卡配置 2、网关配置 3、停止networkManager服务 4、重启网卡服务 二、配置静态网卡 1、修改网络配置 2、修改主机名 三、加载MySQL驱动包 1、准备文件 2、下载地址 3、加载MySQL驱动包 四、安装 cloudera-manager-daemons 五、保存当…

CDH6.3.1安装指南

CDH安装指南!!!! CDH简介 CDH基于Web的用户界面,支持大多数Hadoop组件,包括HDFS、MapReduce、Hive、Pig、 HBase、Zookeeper、Sqoop,简化了大数据平台的安装、使用难度。 Cloudera Manager的功能&#xff…

linux安装cdh

环境准备: linux版本:阿里云ecs,Red Hat 4.8.5,四台机器(一台主节点,三台从节点) cdh版本:cdh-6.1.0-install-soft.tar.gz,离线安装 java版本: jdk1.8.0_18…

ClouderaCDH安装配置说明

实验环境 实验环境:Win7下vmware虚拟机 操作系统:CentOS 6.5 x64 Clouder Manager:5.14.0 CDH:5.14.0 安装说明 官方共给出了3中安装方式:第一种方法必须要求所有机器都能连网,由于最近各种国外的网站被…

CDH 6系列(CDH 6.0.0、CDH 6.1.0、CDH 6.2.0等)安装和使用

日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) CDH 6系列(CDH 6.0.0、CDH 6.1.0、CDH 6.2.0等)安装和使用 CDH5(CDH 5.16.1)安装 CDH 6系列&am…

CDH6.3.2详细安装

CDH(Cloudera’s Distribution, including Apache Hadoop),是Hadoop众多分支中的一种,由Cloudera维护,基于稳定版本的Apache Hadoop构建,并集成了很多补丁,可直接用于生产环境。 CDH优点: 1、提供基于web…

cdh安装及配置

一、修改linux主机名称 vim /etc/sysconfig/network 三台虚拟机修改后重启 centos7修改方法: [rootcentos7 ~]$ hostnamectl set-hostname 要修改的主机名.magedu.com # 使用这个命令会立即生效且重启也…

cdh6.3.2安装

1. 数仓之Cloudera Manager 1.1 CM简介 1.1.1 CM简介 Cloudera Manager是一个拥有集群自动化安装、中心化管理、集群监控、报警功能的一个工具,使得安装集群从几天的时间缩短在几个小时内,运维人员从数十人降低到几人以内,极大的提高集群管理…