彻底理解线程

news/2024/5/5 3:33:42/文章来源:https://www.cnblogs.com/xiaoyangjia/p/16631830.html

1 线程的意义

操作系统支持多个应用程序同时执行,每个应用至少对应一个进程,彼此之间的操作和数据不受干扰。当一个进程需要磁盘IO的时候,CPU就切换到另外的进程,提高了CPU利用率。

有了进程,为什么还要线程?因为进程的成本太高了。

启动新的进程必须分配独立的内存空间,建立数据表维护它的代码段、堆栈段和数据段,这是昂贵的多任务工作方式。如果两个进程之间需要通信,要采用管道通信、消息队列、共享内存等等方式。线程可以看作轻量化的进程,或者粒度更小的进程。线程之间使用相同的地址空间,切换线程的时间远远小于切换进程的时间。一个进程的开销大约是线程开销的30倍左右。

随着操作系统的发展,进程已经演变成了线程容器的角色。进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程,同一进程的所有线程共享该进程的所有资源。

2 详解Java线程

我们以Java语言和JVM为例,了解一下线程的实现原理。

2.1 线程的底层实现

启动一个Java程序会创建一个JVM进程,JVM创建、管理线程本质都是调用操作系统接口。

public class TestThreadStart {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {System.out.println("start thread now ");}, "TestThreadStart");thread.run();System.out.println("the state of thread is " + thread.getState().name());thread.start();System.out.println("the state of thread is " + thread.getState().name());}
}

以上代码演示了使用start方法启动线程,run方法只是执行同步方法,输出结果如下:

start thread now 
the state of thread is NEW
the state of thread is RUNNABLE
start thread now 

JVM源码文件 https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Thread.java 中,可以看到线程启动的start方法调用本地方法start0。

    public void start() {synchronized (this) {// zero status corresponds to state "NEW".if (holder.threadStatus != 0)throw new IllegalThreadStateException();start0();}}private native void start0();

源码文件 https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libjava/Thread.c 中,实现了start0方法映射到JVM本地方法。

static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},{"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},{"isAlive0",         "()Z",        (void *)&JVM_IsThreadAlive},{"suspend0",         "()V",        (void *)&JVM_SuspendThread},{"resume0",          "()V",        (void *)&JVM_ResumeThread},{"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},{"yield0",           "()V",        (void *)&JVM_Yield},{"sleep0",           "(J)V",       (void *)&JVM_Sleep},{"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},{"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},{"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},{"interrupt0",       "()V",        (void *)&JVM_Interrupt},{"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},{"getThreads",       "()[" THD,    (void *)&JVM_GetAllThreads},{"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},{"getStackTrace0",   "()" OBJ,     (void *)&JVM_GetStackTrace},{"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},{"extentLocalCache",  "()[" OBJ,    (void *)&JVM_ExtentLocalCache},{"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},{"getNextThreadIdOffset", "()J",     (void *)&JVM_GetNextThreadIdOffset}
};

在源码文件 https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/thread.cpp 可以看到启动线程依赖系统级方法os::start_thread(thread)

void Thread::start(Thread* thread) {// Start is different from resume in that its safety is guaranteed by context or// being called from a Java method synchronized on the Thread object.if (thread->is_Java_thread()) {// Initialize the thread state to RUNNABLE before starting this thread.// Can not set it after the thread started because we do not know the// exact thread state at that time. It could be in MONITOR_WAIT or// in SLEEPING or some other state.java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),JavaThreadStatus::RUNNABLE);}os::start_thread(thread);
}

在源码文件 https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/os.cpp 找到os::start_thread方法,可以看到系统创建了线程,并且状态设置为RUNNABLE。

void os::start_thread(Thread* thread) {OSThread* osthread = thread->osthread();osthread->set_state(RUNNABLE);pd_start_thread(thread);
}

Linux系统并没有把线程和进程区别对待,无论线程还是进程都是一个数据结构,用task_struct结构体表示,唯一的区别是共享的数据区域不同。

struct task_struct {// 进程状态long              state;// 虚拟内存结构体struct mm_struct  *mm;// 唯一进程号pid_t             pid;// 指向父进程的指针struct task_struct   *parent;// 子进程列表struct list_head      children;// 存放文件系统信息的指针struct fs_struct      *fs;// 进程/线程打开的文件指针struct files_struct   *files;
};

以上代码是 task_struct 的极少部分字段。mm_struct是进程的虚拟内存空间,files_struct是进程将要读写的文件。Linux系统将一切外设和磁盘文件都当做文件处理,files_struct代表所有的IO操作。


从上图可以看到,Linux创建进程和子进程会申请不同的内存空间,读写不同的文件;创建进程和进程下的线程,共享了内存空间,读写一样的文件。因此多线程应用程序要利用锁机制,避免在同一区域写入错乱数据的问题。

2.2 线程的生命周期

操作系统的线程生命周期可以分为五种状态。分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。JVM将线程等待状态细分成两种,一共六种状态。

  • NEW:创建。
  • RUNNABLE:运行中。
  • BLOCKED:受阻塞并等待某个监视器锁。
  • WAITING:无限期地等待。
  • TIMED_WAITING:等待指定时间。
  • TERMINATED:终止。
2.3 线程的优先级

操作系统调度线程有两种方式:

  • 协作式调度:当前线程完全占用CPU时间,执行时间由线程本身控制,直到运行结束,系统才执行下一个线程。可能出现一个线程一直占有CPU,而其他线程等待,导致整个系统崩溃。

  • 抢占式调度:操作系统决定下一个占用CPU时间的是哪一个线程,定期的中断当前正在执行的线程,任何一个线程都不能独占。不会因为一个线程而影响整个进程的执行,但是频繁阻塞和调度会造成系统资源的浪费。

JVM的线程调度默认是抢占式调度,线程调度器按照优先级决定调度哪个线程来执行。线程优先级的范围是1~10,默认的优先级是5,10极最高。线程优先级高的不一定先执行,优先级低只是获得调度的概率低,并不是一定最后被调度。通过setPriority()可以改变线程优先级。

2.4 JVM守护线程

守护线程是一种JVM中特殊的线程,在后台完成一些系统性的服务,比如垃圾回收。应用程序创建的线程叫做用户线程,完成具体的业务操作。程序中所有的用户线程执行完毕之后,不管守护线程是否结束,JVM都会自动结束。任何线程都可以通过setDaemon()设置为守护线程和用户线程,如下代码所示:

public class DaemonThreadDemo {public static void main(String[] args) {System.out.println("--主线程开始--");Thread thread = new Thread(() -> {while (true) {System.out.println("执行守护线程");}});thread.setDaemon(true);thread.start();System.out.println("--主线程结束--");}
}

程序运行结果:

--主线程开始--
--主线程结束--
执行守护线程
执行守护线程
执行守护线程
执行守护线程
执行守护线程
Process finished with exit code 0

当一个应用程序需要在后台持续做某件事情,就是守护线程的典型应用场景。比如开发一款社交软件,开启守护线程持续监听聊天消息。当应用程序退出时,守护线程一定会终止。

参考文章:https://www.codingbrick.com/archives/937.html

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

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

相关文章

软件质量保障流程

一. 软件质量保障流程 1.1 微服务产品的特点 微服务架构下,一个大型复杂软件系统不再是一个单体,而是一系列相互独立的微服务,特点鲜明:每个服务独立,开发技术栈独立 每个服务可以独立开发、部署、发布 服务之间通过轻量级通信机制沟通,常用的是 RESTful APIMicro Servic…

JavaScript实现栈结构(Stack)

Js实现栈结构 一、前言 1.1 什么是数据结构 数据结构就是在计算机中,存储和组织数据的方式。 例如:图书管理,怎样摆放图书才能既能放很多书,也方便取? 常见的数据结构:栈(Stack) 队列(Queue) 链表(Linked List) 集合(Set) 哈希(Hash) 树(Tree) 图(Graph)1.…

Jenkins设置中文

Jenkins是一个持续集成的平台,但是初次安装Jenkins之后,是英文的状态。下面给大家介绍如何将Jenkins设置为中文语言。一、下载Locale插件1点击【Manage Jenkins】选项。2点击【Manage Plugins】选项。 点击【可选插件】选项。 右侧搜索栏输入【Locale】选项。 勾选【插件选项…

URL重定向及绕过方法

1 概述 URL重定向漏洞也称URL任意跳转漏洞,是由于网站信任了用户的输入导致恶意跳转。URL重定向主要用来钓鱼,比如URL跳转中最常见的跳转在登录口、支付口,也就是一旦登录将会跳转任意自己构造的网站,如果设置成指定的URL,则会造成钓鱼等危害。 2 详情 2.1 跳转常用参数 在…

js删除css样式

js删除css样式_百度知道 https://zhidao.baidu.com/question/680409425108037292.html1、如果使用class加的样式的话,可以使用document.getElementById("objid").className=""来清空样式;  2、如果是直接加的style="***"属性的话,可以使用…

ArcGIS API forJavaScript4.x去除地图获取焦点的黑色边框

ArcGIS API forJavaScript4.x去除地图获取焦点的黑色边框CSS /* 去掉地图聚焦边框 */ .esri-view-surface--inset-outline:focus::after {outline: none !important; }箴言:因为这些东西是非常简单的。不要抱怨自己学不会,那是因为你没有足够用心。

NetCDF库编译安装

1. 准备 系统环境,全部是deepin系统自带,并无单独安装deepin 20.6 gcc 8.3.0 g++ 8.3.0 cmake 3.22.1 netcdf安装版本:此次使用最新4.9.0,系统为deepin 20.6,大于4.3.0 源码下载地址,下载netcdf-c-4.9.0.tar.gz: https://downloads.unidata.ucar.edu/netcdf/ netCDF-C S…

el-button点击了按钮会出现保留点击的状态

问题 el-button点击了按钮之后,将鼠标移出按钮,会出现保留点击的状态 再查看它的css样式后,可以看到官方默认设置有:focus的状态规则解决办法 在el-button的css里自定义或者复制原有的color、border-color、background-color三条样式, 然后再重写:hover方法就可以了, 其实…

SpringBoot日志系统——logback

logback是SpringBoot内置的日志处理框架,你会发现spring-boot-starter其中包含了spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 logback。而spring-boot-starter-web包含了spring-boot-starter,可通过引入spring-boot-starter-web后的项目JAR依赖关…

seurat 单细胞数据分析中 VizDimLoadings 函数

前期处理:https://www.jianshu.com/p/fef17a1babc2 #可视化对每个主成分影响比较大的基因集 001、dat <- pbmc[["pca"]]@feature.loadings ## 数据来源 dat[1:3, 1:3] dat <- dat[order(-dat[,1]),][1:29,1] dat <- as…

网页返回顶部的几种方法

网页返回顶部的几种方法 - 腾讯云开发者社区-腾讯云 https://cloud.tencent.com/developer/article/2043539 1,在页面顶部固定一个 返回网页顶部的 按钮.back-to-top {position: fixed;right: 20px;bottom: 10px;width: 100px;height: 30px;text-align: center;line-height: …

Navicat_Keygen_Patch_v5.6

navicat download: Navicat keygen download: Navicat_Keygen_Patch_v5.6 注意事项: 1.运行注册机请断网;2.最好关闭电脑的杀毒软件; 3.无需将注册机放到 Navicat Premium 安装目录下;4.请选择对版本,Products那块;5.安装完成后不要直接运行软件,要先打开注册机 pat…

AI模型集成到业务系统的方式演化

如今,AI模型百花齐放,在业务场景中的使用是越来越多,那AI模型是如何集成到系统中的呢?这几年的集成方式又有何变化呢?在一些早期系统中,模型比较简单,这时,真正在线上系统部署模型时,模型只是以算法的形式出现,模型参数作为算法所需要的“数据”,存储在内存或分布式…

Python教程:文件和读写的详细教程

文件操作的模式 文件操作的模式如下表:1. open 打开文件 使用 open 打开文件后一定要记得调用文件对象的 close() 方法。比如可以用 try/finally 语句来确保最后能关闭文件。 file_object = open(rD:\test.txt) # 打开文件 try:all_the_text = file_object.read( ) # 读文件的…

学习:python进阶 (一)

python解释器多版本共存 1.下载需要的版本安装包 2.增加环境变量,把优先用的python放到第一个 修改python.exe可执行文件,在终端输入对应名称可以指定需要运行的python版本 虚拟环境 python3 -m venv 虚拟环境路径 #新建虚拟环境 cd 虚拟环境路径/Script acticate.bat #开启虚…

04Spring MVC入门

Spring MVC三层架构表现层 业务层 数据访问层MVC(处理表现层)Model:模型层 View:视图层 Controller:控制层底层请求方式 在controller中添加 @RequestMapping("/http")public void http(HttpServletRequest request, HttpServletResponse response) throws IOExce…

Python中itertools

一、介绍 itertools 是python的迭代器,itertools提供的工具相当高效且节省内存 使用这些工具,可创建自己定制的迭代器用于高效率循环 1.count(初值=0,步长=1):1 from itertools import count2 for i in count():3 print i4 if i > 10:5 break6 7 #从0…

R语言中 %||%什么意思?

001、 %||% 函数用于判断 左侧变量是否为NULL, 如果左侧为NULL, 则返回右侧的变量; 否则,返回左侧的变量library(rlang) x = NULL y = 10 x %||% y ## 左侧变量x为NULL, 则返回右侧变量y的值x = 888 x %||% y ## 左侧变量x不为NULL, 则返回左侧变量的值

2022HDU多校第五场 - 1007 Count Set

置换群 + 生成函数 + NTT + 启发式合并/分治 题意 给一个 1-n 的排列 p 和一个非负整数 k,求大小为 k 的 {1, 2, 3,... n} 的子集合 T 的数量,满足即 T 的元素按 p 置换一轮后和自身没有交集 思路\(i\) -> \(p_i\) 连边,找到 m 个环设某个环的大小为 a,要找 b 个元素…

MapReduce计算流程

MapReduce的计算流程 1.1 原始数据File The books chronicle the adventures of the adolescent wizard Harry Potter and his best friends Ron Weasley and Hermione Granger, all of whom are students at Hogwarts School of Witchcraft and Wizardry. 1T数据被切分成块存放…