Java程序员需要掌握的计算机底层知识(三):进程、线程、纤程、中断

本文主要是介绍Java程序员需要掌握的计算机底层知识(三):进程、线程、纤程、中断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

面试高频问题

:进程和线程有什么区别?
:进程是一个程序运行起来的状态(运行态),线程是一个进程中不同的执行路径(线程只是其中一个)。
更为专业的回答:进程是操作系统用来分配资源的基本单位,线程是操作系统用来执行调度的基本单位。

进程、线程、纤程

1、进程

在这里插入图片描述
双击QQ.exe,操作系统通过IO把它从磁盘取出来,放进内存中,形成一个进程。
再双击QQ.exe,又开启一个进程…(同一个可以多次启动,开启多个进程)

进程最重要的资源,是它被分配的自己独立的内存空间

程序开始执行之后,首先有一个main主线程。之后可能会有其他的线程,线程调度执行。
在这里插入图片描述
在这里插入图片描述
线程和进程的区别:
(标准回答)线程共享进程的内存空间,没有自己独立的内存空间。

操作系统的线程是怎么实现的?
不同的操作系统对于线程的实现是不同的。
可以读一下《Linux内核设计与实现》
在这里插入图片描述
在Linux中,进程就是一个fork,会从现有的进程里面开启一个新的子进程。
Linux里面,进程和线程没有太大的区别,线程就是一个普通的进程,只不过和其他进程共享资源(内存空间、全局数据等…)
其他操作系统中(比如Windows,Solaris),有各自LWP的实现,线程是轻量级进程
在这里插入图片描述

协程(纤程)

JVM(HotSpot)和操作系统的线程是一一对应的:JVM空间的一个线程,对应操作系统中的一个线程,这是重量级线程。

操作系统可以开启的线程是有限的(1万个已经很卡了),而纤程用户空间的,不需要与内核打交道,轻量级,切换速度快,可以启动几万个,甚至几十万个都没有问题。

纤程
纤程可以被理解为:线程里的线程,在JVM层级用软件进行内部的线程协调。
纤程是被JVM管理,运行在用户态,操作和调度不经过操作系统内核。
关于纤程的实现:纤程可以分配自己独立的纤程栈,相当于自己实现了一个小小的操作系统级别的调度程序。

纤程的优势
1、占有资源很少。操作系统启动一个线程占用1M的内存空间,而启动一个纤程只需要4K空间
2、由于轻量级,切换简单
3、由于轻量级,可以启动几十万个纤程都没有问题。

2020目前支持内置纤程的语言
Kotlin Scala Go Python(+lib)
Java通过类库可以支持:OpenJDK的loom项目正在做纤程的尝试。目前 JDK 14 还没有支持纤程,期待 JDK 15 吧…

利用Quaser库(不成熟)

如果大规模应用在生产,还是有一些小bug存在的。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>mashibing.com</groupId><artifactId>HelloFiber</artifactId><version>1.0-SNAPSHOT</version><dependencies><!-- https://mvnrepository.com/artifact/co.paralleluniverse/quasar-core --><dependency><groupId>co.paralleluniverse</groupId><artifactId>quasar-core</artifactId><version>0.8.0</version></dependency></dependencies></project>

HelloFiber.java 线程版
耗时7s
在这里插入图片描述

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.SuspendableRunnable;public class HelloFiber {public static void main(String[] args) throws  Exception {long start = System.currentTimeMillis();Runnable r = new Runnable() {@Overridepublic void run() {calc();}};int size = 10000;
​Thread[] threads = new Thread[size];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(r);}for (int i = 0; i < threads.length; i++) {threads[i].start();}for (int i = 0; i < threads.length; i++) {threads[i].join();}long end = System.currentTimeMillis();System.out.println(end - start);}static void calc() {int result = 0;for (int m = 0; m < 10000; m++) {for (int i = 0; i < 200; i++) result += i;}}
}

HelloFiber2.java 纤程版
耗时3s

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.SuspendableRunnable;public class HelloFiber2 {public static void main(String[] args) throws  Exception {long start = System.currentTimeMillis();int size = 10000;
​Fiber<Void>[] fibers = new Fiber[size];for (int i = 0; i < fibers.length; i++) {fibers[i] = new Fiber<Void>(new SuspendableRunnable() {public void run() throws SuspendExecution, InterruptedException {calc();}});}for (int i = 0; i < fibers.length; i++) {fibers[i].start();}for (int i = 0; i < fibers.length; i++) {fibers[i].join();}long end = System.currentTimeMillis();System.out.println(end - start);}static void calc() {int result = 0;for (int m = 0; m < 10000; m++) {for (int i = 0; i < 200; i++) result += i;}}
}

可再优化

  • 目前是10000个Fiber,在1个JVM线程中执行。
  • 可以考虑将10000个计算任务分为10个线程,让10个线程去执行,每个线程执行1000个。

这样既充分利用了操作系统在内核级别对于线程的调度,又充分利用了JVM在用户空间的对于纤程的调度。

纤程的应用场景
  • 纤程 vs 线程池:什么时候用纤程,什么时候用线程池?

纤程应用在很短的计算任务,不需要和内核打交道,并发量高的时候,适合使用纤程。
如果是读文件之类的任务,用线程比较好。

内核线程(了解)

内核线程是内核独用的线程,用来进行一些后台操作,比如垃圾清理等。
在这里插入图片描述

进程创建和启动

在Linux中,fork 底层调用的是 clone,是从当前进程 clone 出一个新的进程来。
在这里插入图片描述

僵尸进程、孤儿进程

1、僵尸进程

在大多数情况下,少量僵尸进程的存在,对操作系统没有太大影响,它基本不再占用任何资源了,它在内存中唯一占用的资源就是PCB了。
僵尸进程的产生原因,有可能父进程是一个daemon进程,C程序开发人员在父进程没有释放子进程,这时会出现僵尸进程。
可以使用内核中的wait函数,释放僵尸进程
在这里插入图片描述
僵尸进程c示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>int main() {pid_t pid = fork();  // 从 A 进程 fork 出来一个子进程 Bif (0 == pid) { // 这段是在 fork 的子进程执行的 Bprintf("child id is %d\n", getpid());printf("parent id is %d\n", getppid());  // main 进程的 id} else {  // 这段是执行的进程 Awhile(1) {}}
}

在这里插入图片描述

2、孤儿进程

孤儿进程产生的原因:子进程还活着,父进程挂了

孤儿进程和孤儿线程没有太大区别,因为在Linux中,进程和线程没有太大区别。
孤儿进程的影响:影响不大。

孤儿进程c示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (0 == pid) {  // 子进程printf("child ppid is %d\n", getppid());  // 打印爹的 id 号sleep(10);  // 子进程睡 10 秒,此过程中父进程已经主动退出了printf("parent ppid is %d\n", getppid());  // 换了个爹} else {  // 这里是父进程printf("parent id is %d\n", getpid());  // 打印自己的 id 号sleep(5);  // 睡 5 秒exit(0);  // 父进程主动退出}
}

运行结果
在这里插入图片描述
进程号1457的进程,它的父亲进程号是1
在这里插入图片描述

进程调度

Linux中特别灵活,有各种各样的调度方案,可以写内核去自定义。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

进程调度

(目前)Linux 2.6 内核版本采用 CFS 调度策略:Completely Fair Scheduler 完全公平

CFS调度策略:按优先级分配时间片的比例,记录每个进程的执行时间,如果有一个进程执行时间不到他应该分配的比例(比如给了你50%,你只用了10%),就优先执行这个进程作为补偿。
在这里插入图片描述
进程调度基本概念
在这里插入图片描述

Linux 默认调度策略:

1、实时进程:先执行

  • 优先级不一样:使用FIFO策略 (First In First Out),优先级最高的先执行
  • 优先级一样 :使用RR策略(Round Robin)轮询

2、普通进程: CFS完全公平策略

在这里插入图片描述

中断

外设的输入随时都有可能到达,cpu如何及时的知道并进行处理呢?

cpu提供中断机制来满足这种需要,当cpu的内部有需要处理的事情发生时,将产生中断信息,引发中断过程,这种中断信息来自cpu内部,还有一种中断信息,来自于cpu外部,当cpu外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向cpu发出相应的中断信息。cpu在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
在这里插入图片描述
如何用硬件的电信号控制软件的输出的?
1、键盘按下后,CPU去查是哪种类型的中断
2、如果这个中断在中断向量表中,CPU到内存中的一个固定位置找到对应的中断处理程序,进行处理

  • 上半场:内核内部处理
  • 下半场:交给软件处理

在这里插入图片描述

硬中断

键盘、网卡、打印机、时钟计时器的中断,都属于硬中断。

软中断

软件产生的中断,做系统调用时,需要惊动内核。
比如想要和硬盘打交道,需要调用内核,让内核去做硬盘的访问,这是软中断。
软件给内核一个 0x80中断 信号,然后去调用200多个函数中的一个。

阿里面试题:解释80中断的执行过程
int 0x80 是C语言的函数,现在 intel 已经直接移植到了硬件层面,sysenter原语是汇编语言实现的。
bx cx dx si di存储着内核函数的5个参数
比如,InputStream中的read()方法:
在这里插入图片描述

从汇编角度理解80软中断

搭建CentOS汇编环境
yum install nasm

;hello.asm
;write(int fd, const void *buffer, size_t nbytes) 调用了内核空间的write函数
;fd 文件描述符 file descriptor - linux下一切皆文件,网络、硬盘、内存、打印机、屏幕都可以对应到各自的文件描述符
​
section datamsg db "Hello", 0xA ;定义一个字符串hello   0xA是换行len equ $ - msg
​
section .text
global _start
_start:
​mov edx, lenmov ecx, msgmov ebx, 1 ;文件描述符1 表示std_out标准输出mov eax, 4 ;write函数系统调用号 4int 0x80 ;对外输出
​mov ebx, 0mov eax, 1 ;exit函数系统调用号int 0x80

编译:nasm -f elf hello.asm -o hello.o(编译成为二进制)
链接:ld -m elf_i386 -o hello hello.o(和其他引用到的类库进行连接,才能生成可执行文件)

一个程序的执行过程,要么处于用户态,要么处于内核态

在这里插入图片描述

这篇关于Java程序员需要掌握的计算机底层知识(三):进程、线程、纤程、中断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/557081

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作