Java线程实现

news2024/11/17 3:34:22

内容引用自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》

线程的实现

我们知道,线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和 执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度。目前线程 是Java里面进行处理器资源调度的最基本单位,不过如果日后Loom项目能成功为Java引入纤程 (Fiber)的话,可能就会改变这一点。
主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作 的统一处理,每个已经调用过start()方法且还未结束的java.lang.Thread类的实例就代表着一个线程。我 们注意到Thread类与大部分的Java类库API有着显著差别,它的所有关键方法都被声明为Native。在 Java类库API中,一个Native方法往往就意味着这个方法没有使用或无法使用平台无关的手段来实现 (当然也可能是为了执行效率而使用Native方法,不过通常最高效率的手段也就是平台相关的手 段)
** 实现线程主要有三种方式:使用内核线程实现(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],除了有明确的需求外(譬如以前在不支持多线程的操作系统中的多线程程序、需要 支持大规模线程数量的应用),一般的应用程序都不倾向使用用户线程。Java、Ruby等语言都曾经使 用过用户线程,最终又都放弃了使用它。但是近年来许多新的、以高并发为卖点的编程语言又普遍支 持了用户线程,譬如Golang、Erlang等,使得用户线程的使用率有所回升。

3.混合实现

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一 起使用的实现方式,被称为N:M实现。在这种混合实现下,既存在用户线程,也存在轻量级进程。 用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以 支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁, 这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来 完成,这大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量 比是不定的,是N:M的关系

Java线程实现

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()方法可以主动让出执行时间,但是如果想要主动获取 执行时间,线程本身是没有什么办法的。在这种实现线程调度的方式下,线程的执行时间是系统可控 的,也不会有一个线程导致整个进程甚至整个系统阻塞的问题。Java使用的线程调度方式就是抢占式 调度。与前面所说的Windows 3.x的例子相对,在Windows 9x/NT内核中就是使用抢占式来实现多进程 的,当一个进程出了问题,我们还可以使用任务管理器把这个进程杀掉,而不至于导致系统崩溃。

虽然说Java线程调度是系统自动完成的,但是我们仍然可以“建议”操作系统给某些线程多分配一 点执行时间,另外的一些线程则可以少分配一点——这项操作是通过设置线程优先级来完成的。Java 语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY)。在两 个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。

不过,线程优先级并不是一项稳定的调节手段,很显然因为主流虚拟机上的Java线程是被映射到 系统的原生线程上来实现的,所以线程调度最终还是由操作系统说了算。尽管现代的操作系统基本都 提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应,如Solaris中线程有 2147483648(2的31次幂)种优先级,但Windows中就只有七种优先级。如果操作系统的优先级比Java 线程优先级更多,那问题还比较好处理,中间留出一点空位就是了,但对于比Java线程优先级少的系 统,就不得不出现几个线程优先级对应到同一个操作系统优先级的情况了。下表展示了Java线程优 先级与Windows线程优先级之间的对应关系,Windows平台的虚拟机中使用了除 THREAD_PRIORITY_IDLE之外的其余6种线程优先级,因此在Windows下设置线程优先级为1和2、3 和4、6和7、8和9的效果是完全相同的。
在这里插入图片描述

线程优先级并不是一项稳定的调节手段,这不仅仅体现在某些操作系统上不同的优先级实际会变 得相同这一点上,还有其他情况让我们不能过于依赖线程优先级:优先级可能会被系统自行改变,例 如在Windows系统中存在一个叫“优先级推进器”的功能(Priority Boosting,当然它可以被关掉),大 致作用是当系统发现一个线程被执行得特别频繁时,可能会越过线程优先级去为它分配执行时间,从 而减少因为线程频繁切换而带来的性能损耗。因此,我们并不能在程序中通过优先级来完全准确判断 一组状态都为Ready的线程将会先执行哪一个

线程的状态

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

  1. 新建(New):创建后尚未启动的线程处于这种状态。
  2. 运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可 能正在执行,也有可能正在等待着操作系统为它分配执行时间。
  3. 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线 程显式唤醒。以下方法会让线程陷入无限期的等待状态:

■没有设置Timeout参数的Object::wait()方法;
■没有设置Timeout参数的Thread::join()方法; ■LockSupport::park()方法。

  1. 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待 被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:

■Thread::sleep()方法;
■设置了Timeout参数的Object::wait()方法;
■设置了Timeout参数的Thread::join()方法;
■LockSupport::parkNanos()方法;
■LockSupport::parkUntil()方法。

  1. 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到 一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时 间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  2. 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

在这里插入图片描述

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

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

相关文章

React框架入门

React是用于构建用户界面的JavaScript库, 起源于Facebook的内部项目,该公司对市场上所有 JavaScript MVC框架都不满意,决定自行开发一套,用于架设Instagram的网站。于2013年5月开源 一、React简介 React以声明式编写 UI&a…

0119 动态规划 Day8

剑指 Offer 10- I. 斐波那契数列 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: F(0) 0, F(1) 1 F(N) F(N - 1) F(N - 2), 其中 N > 1…

计算机的硬件系统和软件系统的关系

计算机的硬件系统和软件系统的关系是缺一不可。 硬件它是所有软件运行的物质基础。 与硬件直接接触的是操作系统,它处在硬件和其他软件之间,表示它向下控制硬件,向上支持其他软件。 在操作系统之外的各层分别是各种语言处理程序、数据库管理…

CleanMyMacX4.12.1Crack版本频繁弹密码 菜单浮窗无法显示显示空白解决办法

你们有没有出现过在使用 CleanMyMac 清理系统垃圾文件的时候会频繁弹出输入开机密码?那么该如何解决这个问题呢?跟着小编来看看解决方法吧! 频繁输入密码 更新CleanMyMacX到4.12.1的Crack版本之后,发现做一些操作要一直输入密码&…

科技云报道:PingCAP黄东旭:Serverless是数据库的未来形态

科技云报道原创。 30年前,程序员要想写代码,必须使用复杂的汇编语言。 但在今天,几乎没有程序员知道如何使用汇编语言,更加简易的高级语言如C,C#,JAVA,Rust,Go已成为开发主流。 随…

分布式文件存储系统FastDFS[3]-通过Docker安装并且从客户端进行上传下载测试

一、FastDFS安装 FastDFS的安装我们还是通过Docker来安装实现吧,直接在Linux上还装还是比较繁琐的,但就学习而言Docker安装还是非常高效的。Docker环境请自行安装哦,不清楚的可以看看我的Docker专题的内容。https://blog.csdn.net/qq_3852657…

【jenkins】1. 安装jenkins (docker-compose)

环境 ubuntu 20docker服务器 ip:xxx.xxx.xxx.xxx 步骤 1. 【编写安装文件】windows下 - 编写 docker-compose.yaml version: "3.1" services:jenkins:image: jenkins/jenkinsrestart: alwayscontainer_name: jenkinsports:- 58080:8080- 50000:50000volumes:- ./…

5.31 综合案例2.0 - 在线MP3音乐播放器

综合案例2.0 - 在线MP3音乐播放器一、案例说明二、准备器件三、案例连线四、代码代码说明复制五、测试一、案例说明 本案例制作一个联网下载声音文件的MP3播放器。 案例功能说明: 案例使用一块IC035串口屏和两个按键通过按键可以切换音乐,下载播放或删除…

瑜岿科技综合能源管理系统助力企业节能降耗工作

能源是国民经济的基础,更是城市赖以发展的动力。优化能源结构、大力发展可再生能源、提高机房能效、实现建筑智慧节能是行业绿色发展重点。在国家碳达峰碳中和的重大战略决策背景下,我国能源体制改革深入推进,能源生产和消费发生重大变革&…

如今社交电商不可忽略的一个点——消费增值

消费增值是一个基于消费返利理论的全新商业消费生态,一个让用户从优惠消费—免单消费—挣钱消费,所突破的新消费、新业态模式—增值消费,一个从电商化的平台走向品牌化的平台,让平台脱离单打独斗的方式,通过商家联盟方…

编译原理 2 - 词法分析

第3章 词法分析3.1 词法分析器的功能和结构3.2 状态转换图3.3 正则文法 和 正则表达式3.4 有限自动机 DFA与NFA测试第3章 词法分析 重点:① 词法分析器的输入、输出;② 用于识别符号的状态转移图的构造;③ 根据状态转移图实现词法分析器 难点…

【MATLAB教程案例55】GoogleNet网络的MATLAB编程学习和实现,以步态识别为例进行仿真分析

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 1.软件版本 2.GoogleNet理论概述

基于Qt的桌面客户端组件化框架DT 开源啦

这个是本人在工作中基于QT开发的组件化桌面开发框架,目前打算开源出来提供给大家,节省大家的开发时间和效率,希望对大家的开发有所帮助,也欢迎提出意见和改进建议 1. 为什么开源DT框架 DT框架,本身就是基于QT为基础搭…

深度学习——含并行连接的网络(GoogLeNet)笔记

GoogLeNet中基本的卷积块是Inception块。 1.Inception块:4个路径从不同的层面抽取信息,然后在输出通道合并 ①1*1的卷积层,减少通道数,降低模型的复杂度 ② 1*1的卷积层,减少通道数,降低模型复杂度。然后…

volatile关键字和synchronized关键字

参考博客:https://www.cnblogs.com/cg-ww/p/14540450.html 1、volatile的作用:关键作用是使变量在多个线程之间可见 程序并没有因为我修改之后结束运行,因为线程对共享变量具有不可见性,main线程修改布尔值之后,子线…

【读点论文】MicroNet: Towards Image Recognition with Extremely Low FLOPs,在极高限制下的轻量化网络

MicroNet: Towards Image Recognition with Extremely Low FLOPs Abstract 这篇论文旨在以极低的计算成本解决性能大幅下降的问题。提出了微分解卷积,将卷积矩阵分解为低秩矩阵,将稀疏连接整合到卷积中。 提出了一个新的动态激活函数-- Dynamic Shift …

精准管控 | AIRIOT数字油库智能化解决方案

在油库管理的过程中,储油罐区普遍存在分布空间范围广、安全防爆要求高、监控点多、布线复杂、自动化系统集成难度大等问题,传统的油库管理手段相对落后、管理环境复杂,企业在监测监控、设备设施管理、日常运行等方面存在诸多难点:…

Openldap2.5.13编译安装

Openldap2.5.13编译安装 本篇文章没有很全面,采用的是编译安装,yum安装参考我的另外一篇文章 一、准备 openssl-1.1.1.tar.gz 下载地址:https://ftp.openssl.org/source/old/1.1.1/openssl-1.1.1.tar.gz openldap-2.5.13.tgz 下载地址&#…

APS计划排产软件在仪器仪表行业的应用

仪器仪表是指用以产生、测量精密量的设备和装置,包括对精密量的观察、监视、测定、验证、记录、传输、变换、显示、分析处理与控制。仪器仪表是仪器仪表的一个重要分支。伴随国家出台政策大力发展精密仪器仪表下游行业,下游应用领域对精密仪器仪表的需求…

React 学习笔记:组件生命周期

组件生命周期 生命周期一个事物从创建到最后消亡经历的过程,在这个过程中会有不同的状态(新生儿、少年),所以在 16.8 版本以前生命周期只有类组件才有,函数组件是没有这个概念的,因为它没有状态&#xff0…