文章目录

  • 前言
  • 一、操作系统的基础概念介绍
    • 1.并行与并发
    • 2.用户态与内核态
    • 3.执行流
  • 二、内存管理
    • 1.Java程序员眼中的内存
    • 2.线性地址和物理地址
    • 3.进程间通信
  • 三、研究操作系统实现时,面临的问题
    • 1.死锁问题:再分配资源时,如何避免死锁问题
  • 四、线程
    • 1.进程与线程的问题讨论
    • 2.JVM中规定的线程
  • 总结

前言

操作系统是学习多线程前提,对操作系统中的相关概念有比较深入的了解,会对线程的学习有很大的帮助。本篇文章主要阐述操作系统中的各种重要的概念,并对线程做详细的介绍。


一、操作系统的基础概念介绍

1.并行与并发

并行(parallel):进程为真的同时在执行相关操作,也就是在微观视角下的同一时刻,是有多个指令在同时执行,所以并行只会发生在多CPU这种多核场景下。
并发:进程为假的同时在执行相关操作,也就是说在微观视角下,表现为一次只执行一个进程,但是在宏观角度,也就是用户看到的,是多个进程在“同时”执行。

2.用户态与内核态

用户态(user space):指令只能访问自己的内存。
内核态(kernel space):指令可以访问所有内存。
两者比较,用户态的性能较好,内核态的性能较差。

3.执行流

执行流(excution flow):拥有独立PC的一套指令;不同的执行流从现象上看起来是完全独立的。

二、内存管理

记住一个概念,内存管理都是从空间上划分的。
大致划分结果:[内核使用的内存][分配普通进程使用的内存][空闲空间]
但是空间划分不保证是连续的。

1.Java程序员眼中的内存

JVM的内存空间分为:堆区、栈区、方法区…这一块是属于Java应用和JVM中的概念,但是JVM又是作为了操作系统中的一个普通进程,所以,程序员操作的内存就指的是这一块,说白了就是内存划分中分配普通进程的内存。下面,用一张图说明这个概念:

2.线性地址和物理地址


由上图,我们可以看到:
线性地址:就是物理地址被操作系统转换之后的地址,是虚拟地址。
物理地址:是真实存在于内存中的地址。
由此引申出以下知识点:

  • 没有线性地址的情况,同一个程序的多次运行,会生成不同的进程,那我们能否保证同一个进程就一定能被放到内存中的同一个位置呢?
    答案显然是不能的,因为如果程序员可以直接看到物理地址,那就意味着程序员必须关心一个问题——不同进程如果出现在内存的不同位置,那么在程序中处理地址时,必须考虑地址不同带来的复杂性,这也就说明如果没有线性地址,进程的处理一定会出错。所以说,引入线性地址之后,程序员根本就不会再考虑这类复杂性了。
  • 操作系统分配出来的空间只是线性地址空间,实际的物理内存是不会真实的反映出来的,只有在访问这段内存时才会被分配。

3.进程间通信

  • 概念引入:理论上,不同的进程之间是独立的,但是实际上,往往是多个进程之间相互配合,来完成复杂度工作。例如,使用MySql的时候,需要通过workbench进程和MySql服务器进程进行通信,来实现对数据的增删查改。所以,就有了就有了进程之间交换数据的必要性。
  • 问题引入:操作系统进行资源分配是以进程为基本单位进行分配的, 也包括内存。现在有两个进程A和B,操作系统会将内存分配个A进程,不会分配给B进程。所以,进程A、B通过内存来进行数据间的交换的可能性就完全不存在了。也就是说,此时进程A和进程B是隔离的。为此,操作系统专门提供了一套机制,用于进程之间进行必要的数据交换——进程间通信机制。
  • 进程间通信的常见方式:管道(pipe)、消息队列(message queue)、信号量(semaphore)、信号(signal)、共享内存(shared memory)、网络(network)。
  • 内存管理中主要研究的问题:管理哪些内存已经被分配,哪些内存暂未分配?已经分配出的内存,何时进行回收,如何进行回收?物理地址与线性地址的转换;内存碎片等。

三、研究操作系统实现时,面临的问题

1.死锁问题:再分配资源时,如何避免死锁问题

  • 经典问题:
    描述死锁:哲学家问题,哲学家大部分时间在思考问题,在思考间隙,需要吃饭,在哲学家的左边有一只筷子,右边有一只筷子,哲学家需要同时拿到左边的筷子(请求资源)和右边的筷子(请求资源)才能吃饭。这个时候会遇到多个哲学家同时拿筷子(请求资源)的情况,就会发生死锁。
    解决方法:银行家算法(打破死锁的充分条件)。

  • 注意:此处所说的死锁与多线程中的死锁,有内在概念上的关联性,但是严格意义上说,不是一回事。

四、线程

1.进程与线程的问题讨论

  • 目前讨论的都是操作系统层面上的线程(thread)。

  • 进程(process)和线程(thread)的关系
    进程与线程是1:m的关系:
    一个线程一定属于一个进程;一个进程下可以允许有多个线程。
    一个进程内至少有一个线程,通常这个一开始就存在的线程称为主线程(main thread)。
    主线程和其他线程之间地位是完全相等的,没有任何特殊性。

  • 为什么操作系统要引出线程(thread)这一概念?
    由于进程这一个概念本来就是资源隔离的,所以进程之间进行数据通信注定是一个高成本的工作。在现实中,一个任务需要多个执行流一起配合完成工作,是非常常见的,所以就需要一种方便数据通信的执行流,线程就承担了这一职责。

  • 什么是线程?
    线程是操作系统进行调度(分配CPU)的基本的单位;
    在这一概念中,线程变成了独立执行流的承载概念;而进程退化成了只是资源(不含CPU)的承载概念。

  • 进程和线程概念的区别剖析
    进程:操作系统进行资源分配的基本单位(不含CPU资源)。
    线程:操作系统进行调度的基本单位(CPU资源),也就是执行流的承载单位。
    例如,运行一个程序,没有线程之前,OS创建进程,分配资源,给定一个唯一的PC,进行运行。
    有了线程之后,OS创建进程,分配资源。创建线程(主线程),给定一个唯一的PC,进行运行。
    程序的一次执行过程表现为一个进程,main所在的线程就是主线程。主线程中可以运行对应的操作来创建运行其他线程。
    由于进程把调度单位这一个职责让渡给线程了,所以,使得单纯进程的创建销毁适当简单;
    由于线程的创建和销毁不涉及资源分配、回收的问题,所以,通常理解,线程的创建/销毁成本要低于进程的成本。

2.JVM中规定的线程

  • “Java线程”与“操作系统线程(原生线程)”
    不同JVM有不同的实现,它们的外在表现基本一致,除了极个别的几个现象。Java线程,一个线程异常关闭,不会连坐。我们使用的HotSpot实现(JVM)采用,使用一个OS线程来实现一个Java线程。
    Java 中由于有JVM 的存在,所以使得Java中做多进程级别的开发基本很少。Java中的线程还克服了很多OS线程的缺点。所以,在Java开发中,我们使用多线程模型来进行开发,很少使用多进程模型。

  • Java线程在代码中如何体现?
    调用java.lang.Thread类(包括其子类)的一个对象

  • 如何在代码中创建线程?
    (1)通过继承Thread类,并且重写run方法。实例化该类的对象->Thread对象。
    (2)通过实现Runhable接口,并且重写run方法。实例化Runnable对象。利用Runnable对象去构建一个Thread对象。
    Runable——让这个线程去完成的工作。

  • 启动线程
    当有一个Thread对象时,调用其start()方法。

  • 注意
    (1)一个已经调用过start(),不能再调用start()了,start()只允许工作在“新建”状态下,再调用就会有异常发生;
    (2)干万不要调用成 run()。因为调用run方法,就和线程没关系了,完全是在主线程下在运行代码。

  • 多线程模型图

  • 线程代码示例

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("正在的执行起来");}
}
public class Main {public static void main(String[] args) {MyThread t = new MyThread();// 通过调用 Thread 对象的 start 方法,来开始线程的运行(线程中的代码运行起来)t.start();}
}

(1)如何理解t.start()做了什么?
t.start()只做了一件事情:把线程的状态从新建变成了就绪。不负责分配CPU。
(2)如下代码是先执行主线程还是先执行子线程?

public class AboutThread {static class SomeThread extends Thread {@Overridepublic void run() {int i = 0;while (true) {System.out.println("我是另一个线程(执行流 B): " + (i++));   // 语句1try {TimeUnit.MILLISECONDS.sleep(139);   // 语句2} catch (InterruptedException e) {}}}}public static void main(String[] args) {// 在主线程中,利用 SomeClass 对象,创建一个新的线程出来SomeThread st = new SomeThread();st.start();int i = 0;while (true) {System.out.println("我是主线程(执行流 A): " + (i++));     // 语句1try {TimeUnit.MILLISECONDS.sleep(257);   //  语句2} catch (InterruptedException e) {}}}
}

分析,线程把加入到线程调度器(不区分是OS还是JVM 实现的)的就绪队列中,等待被调度器选中分配CPU。从子线程进入到就绪队列这一刻起,子线程和主线程在地位上就完全平等了。先执行子线程中的语句还是主线程中的语句理论上都是可能的,所以,哪个线程会被选中分配CPU,完全是随机的。
但是t.start()是主线程的语句。换言之,这条语句被执行了,说明主线程现在正在CPU上(主线程是运行状态)。所以,主线程刚刚执行完t.start()就马上发生线程调度的概率不大,大概率还是t.start()的下一条语句就先执行了,也就是主线程中的语句会先被打印出来。

  • 什么情况下出现线程调度?
    (1)CPU 空闲
    当前运行着的CPU执行结束了 运行->结束
    当前运行着的CPU等待外部条件 运行->阻塞
    当前运行着的CPU主动放弃 运行->就绪
    (2)被调度器主动调度
    高优先级线程抢占
    时间片耗尽(这个情况最常见)

  • 注意:在多线程中,明明代码是固定的,但会出现现象是随机的可能性,主要原因就是调度的随机性体现在线程的运行过程中。

  • 我们写的无论是Thread的子类还是Runnable的实现类,只是给线程启动的“程序"所以,同一个程序,可以启动多个线程。
    代码示例:

public class Main {public static void main(String[] args) {MyThread t1 = new MyThread();   // 用同一个“程序”启动了多个线程t1.start();MyThread t2 = new MyThread();   // 用同一个“程序”启动了多个线程t2.start();MyThread t3 = new MyThread();   // 用同一个“程序”启动了多个线程t3.start();MyThread t4 = new MyThread();   // 用同一个“程序”启动了多个线程t4.start();}
}

总结

本篇文章主要对操作系统中涉及的相关概念进行了详细的解释,然后就是JVM线程,也就是Java初学者遇到的线程,对这一部分做了全面的论述和代码演示。希望对这一部分存在困惑的朋友有所帮助。

操作系统中的概念详解相关推荐

  1. Java中多线程概念详解

    在操作系统中,两个比较容易混淆的概念是线程(thread)与进程(process).操作系统中的进程是一个计算机程序的运行实例.计算机程序中包含了需要执行的指令,而进程则表示正在执行的指令,对同一个计 ...

  2. 操作系统中的虚拟内存详解

    https://blog.csdn.net/dan15188387481/article/details/49536317 操作系统中的内存管理技术是十分复杂的.现在的操作系统基本都使用逻辑地址和物理 ...

  3. 【Linux操作系统】进程概念详解

    目录 冯诺依曼体系结构 操作系统OS 概念 为什么要有OS OS的定位 如何理解管理 系统调用和库函数概念 进程! 概念 PCB 进程VS数据 task_ struct内容分类 通过系统调用创建进程- ...

  4. 操作系统中磁盘调度算法详解

    磁盘调度算法 (1)先来先服务 (2)最短寻道时间优先 (3)电梯算法 (4)单向扫描调度算法 (1) 磁盘调度 当多个访盘请求在等待时,采用一定的策略,对这些请求的服务顺序调整安排,旨在降低平均磁盘 ...

  5. 操作系统之银行家算法—详解流程及案例数据

    操作系统之进程调度--优先权法和轮转法(附上样例讲解) 操作系统之银行家算法-详解流程及案例数据 操作系统之多线程编程-读者优先/写者优先详解 操作系统之存储管理--FIFO算法和LRU算法 操作系统 ...

  6. python中的GIL详解

    python中的GIL详解 参考Python-- GIL 锁简述 GIL是什么 首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就 ...

  7. 零基础学习PHP编程——基本概念详解之域名解析与DNS缓存

    基本概念详解之一--域名解析与DNS缓存 注意: 本文主要介绍了域名解析流程即DNS缓存相关知识 [转载请注明] 欢迎交流QQ群: 640765823 一. DNS是什么? DNS(Domain Na ...

  8. ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path.之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开 ...

  9. Linux操作系统上lsof命令详解

    Linux操作系统上lsof命令详解 2011-10-08 18:31:31 http://xjsunjie.blog.51cto.com/999372/682865 标签:Linux lsof命令 ...

最新文章

  1. windows下 Source Monitor代码度量工具的使用
  2. ubuntu如何禁用更新?
  3. 每秒8.8亿次请求!让数据存得起,看得见 - 云原生多模数据库Lindorm 2020双十一总结
  4. python关于字典嵌套字典,列表嵌套字典根据值进行排序
  5. C语言各种类型数据的输出显示
  6. Websocket教程SpringBoot+Maven整合(详情)
  7. Python高级专题 - 类型转换的魔术方法
  8. Kernel: Do NOT use global variable as possible as you can 尽量不使用全局变量
  9. 文后参考文献著录规则 自动生成器 HTML
  10. 小熊在线一键重装系统教程
  11. 【数据分析师---数据可视化】第二章:plotly绘图进阶篇(地图可视化,动态数据可视化)
  12. 电脑软件推荐安装列表
  13. Java项目服务器cpu占用100%解决办法
  14. Linux内核之 module_init解析 (下)
  15. 微信小程序阅读器功能
  16. (5.1)Typora——Typora 图像保存和上传( 本地 + PicGo + SMMS/Gitee)
  17. pip uninstall
  18. onnx优化系列 - 获取中间Node的inference shape的方法
  19. DBMS的完整性违约处理机制
  20. unsupported_grant_type

热门文章

  1. 物联网学习之旅:微信小程序控制STM32(三)--STM32代码编写
  2. 标签系统mysql设计_关于tag标签系统的实现
  3. LPC2294对片外EEPROM(24C04)进行读写操作,如何确定24C02EEPROM地址
  4. va_start函数的使用
  5. Handle机制详解
  6. 算法(Algorithm)
  7. 浅谈xhr和fetch
  8. H5 App开发技术如何进行选型 ?
  9. 阿里云国际版核心渠道商
  10. pdf压缩工具怎么用?如何压缩pdf