多线程(一)

计算机的基础:

计算机工作过程

冯诺依曼体系结构:cpu 存储器 输入设备 输出设备
cpu 中央处理器处理:算术运算和逻辑判断等

1.cpu最核心的一个指标–主频:

90GHz Hz频率–一秒钟能算多少次 G–10的9次方–19亿次

  • 一秒钟执行19亿个时钟周期
    • 时钟周期:由于cpu内部的结构非常复杂,需要让cpu的各个部位相互配合,所以出现时钟周期来进行调节,每个时钟周期里面都可以干一些工作,即程序员编写的程序,最终变成指令,在cpu内进行
    • 执行:由源代码文件通过编译成exe可执行文件,此时这个可执行文件是存储在硬盘上的,电脑双击可执行文件,此时操作系统就会将可执行文件加载到内存中,该文件包括程序需要执行的指令(二进制的数据)和指令多所赖的数据;就像射箭,🗡首先是存储在箭筒里面的,从箭筒里面抽🗡放到弓箭上就相当于双击把exe执行起来,将指令和数据加载到内存中,然后就开始执行指挥官的指令即cpu真正开始执行指令
    • cpu 1.cpu将内存中的数据读取到cpu中存储到寄存器里(cpu内部存储数据的单位)2.cpu解析数据3.cpu执行数据;简单粗暴的认为以上操作每一个至少消耗一个时钟单位,但实际cpu为了提高效率,会引入流水线式的作业
    • .java>.class>JVM读指令,解析指令,执行指令>转换cpu能认识的二进制指令
    • 不同的cpu支持的指令是不一样的,cpu有架构体系差别
      cpu发展一直按照指数规律发展,摩尔定律–每个18个月,芯片集成程度提高一倍,运算速度提升一倍,成本降低一半

2.操作系统 操作系统功能:

  • 向上给软件提供稳定的运行环境=软件可以通过操作系统完成一些工作
  • 向下管理各种硬件设备

常见的操作系统
pc端: Windows10/11 Linux mac os
移动端:iOS android

3.操作系统的定位

计算机系统的分层:

  • 应用程序
  • 操作系统范畴
    • 系统调用

      操作系统提供的API,引用程序调动这里的api才能工作,java为了能跨平台,在JVM中已经对各种常见的系统调用进行了封装,不必直接使用系统调用,直接调用JVM里面封装好的一些java代码就可以了
      例如:System.out.println(“hello”);这句代码,操作系统就给应用程序提供了:writer系统调用,可以写入一个程序;和提供了stdout特殊文件,对应到显示器硬件设备.

    • 操作系统内核

      实现了操作系统最核心的功能:内存管理 硬盘管理 进程管理 设备管理

    • 驱动程序

      由于硬件厂商很多,硬件具体的实现也多,设计操作系统内核的时候难以全部识别,所以需要驱动程序,来让系统内核认识并且管理硬件,本质上是个软件

  • 硬件

什么是进程?

进程又叫做任务,一个跑起来的程序就称为进程
一个qq.exe是一个可执行文件,存储在硬盘上的静态的,当双击加载到内存中去然后跑起来的程序叫进程,此时才会执行程序里面的指令,这个才是动态的;通过任务管理器可以看到进程;其中服务主机等是系统启动自动运行的后台进程/服务进程,程序是静态的,而进程是动态的程序.

思考:影响电脑卡不卡的原因?
安装程序多?运行程序?是后者;
因为安装程序多不多只是占用了硬盘空间,在你双击运行前,不占用内存空间,也不占用cpu资源,不会影响计算机卡不卡;只要不是硬盘空间块没了,硬盘的剩余空间多少,对于系统的流畅是没有影响的;而c盘快满的时候,可能会卡;因为很多操作系统,当内存空间不够的时候,会占用到一部分硬盘空间作为"交换区";
运行程序多了,进程多了就会占用cpu和内存资源

如何管理进程

首先管理进程有两步,描述和组织:

  • 描述:通过结构体来详细的描述一个进程的各种属性/信息;这个结构体就称为pcb(又叫进程控制块)
  • 组织:通过一定的数据结构,把用于描述的实体(pcb)放到一起,并且进行增删改查;系统中常用双向链表;
  • 一个进程可能对应多个pcb;系统管理pcb的链表也可能有多个
    所以可以简化成:
    创建一个进程,就是创建一个pcb,并加到链表上
    销毁一个进程,就是从链表中删除对应的pcb节点
    查看任务管理器就是在遍历整个链表;

pcb里具体描述什么信息(进程里面有哪些关键要素)?

  • pid 进程的身份标识 一个主机,同一时刻,只会给一个进程一个pid,用于区分进程
  • 内存指针 分配内存空间干嘛用 由于我们知道要将硬盘文件中的核心数据加载到内存中去,必然要在进程中给这些核心数据分配内存空间;而这块内存空间有很多区域来分别放置数据/指令/维护运行状态等;所以我们需要内存指针来分配哪块空间是干嘛的.
  • 文件描述符表 记录当前进程都打开了哪些存在硬盘上的文件,方便后续针对这些文件进行增删改查.相当于管理硬盘空间

所以内存指针和文件描述符;前者表述进程持有的内存资源,后者描述进程持有的文件资源;所以进程是操作系统分配资源的基本单位

其他与进程调度有关的属性:

  • 进程调度就是通过"并行""并发"的方式,让计算机可以"同时"执行多个任务(只有多核支持);
    为什么呢?因为cpu资源有限,而进程通常有
    • 并行执行 每个cpu核心上,都可以独立运行一个进程(运行进程的相关) 多个cpu很多.所以可以
      • 通过提高cpu的运算能力(通常与集成程度密切相关->单位面积下的元件越多,集成程度越高),cpu现在最细的是5nm制程,提升集成程度很难.但是虽然单核提升能力有限,可以搞多核.6核cpu(同时跑6个任务)
      • 以及通过进程调度实现多任务的操作系统核心就可以同时运行多个任务
    • 并发执行 一个cpu核心先运行进程1,再运行进程2,再进程3;不断的切换,只要微观上切换得够快就可以看起来是多个进程同时运行
      而以下的属性存在的意义就是为了实现进程调度
  • 进程状态—阻塞状态和就绪状态
    由于并发执行实在一个cpu核心上执行不停切换多个进程;所以需要根据每个进程的状态进行切换.阻塞状态的进程是无法调度到cpu上执行的,只有就绪状态的进程才可以上cpu来执行
  • 进程优先级
    系统调度的时候,就会根据优先级,来给进程进行安排时间;虽然创建进程的时候,可以通过一些系统调度来干预优先级,但整个干预是相对的.
  • 进程上下文
    进程a在cpu上执行了一段时间后,要切换给别的进程,就需要保存a运行的中间结果到cpu的寄存器中,下次再执行a时,读档,继续执行.
    对于进程来说,上下文就是cpu的寄存器里面的值
  • 进程记账信息
    记录这些进程的执行时间,用执行指令的条数衡量
    执行进程的时候,虽然优先级,但是不能让有些进程无法使用cpu,通过时间表就可以监视哪些进程没有使用cpu执行

进程调度涉及的另外一个概念:

虚拟地址空间–独立性
原因:不能让进程出现内存越界访问 会产生进程间的相互影响(解引用 野指针:保存了一个无效的内存地址,解引用这个内存)
为了解决这个问题,就需要每个进程都有各自的内存空间,不是真正的物理地址.需要通过硬件设备(mmu-集成在cpu上)来映射到物理地址,避免对物理内存造成影响.
使用虚拟地址空间,就认为进程之间存在了隔离性,一个进程不能这直接访问另一个进程的内存空间.防止干扰的操作,提升了系统的稳定性(不要把鸡蛋放到同一个篮子里)
在这里插入图片描述

但是同时有新的问题,有些需求场景,就是需要多个进程相互配合的,进程隔离了,就很难进行交互;所以系统引入了一个机制,进程间通信:核心原则就是,找一个多个线程都能访问到的公众资源,然后基于公共资源来交换数据.

综合,进程是要求独立性的,如果想要通讯,根据特定的进程间通信机制(基于文件,基于socket(网卡)).类似于面向对象中的封装.

进程创建/销毁步骤:
1.创建pcb
2.给进程分配/释放资源(内存/文件),赋值到pcb中(提高速度的关键)
3.把pcb插入链表

由于创建销毁线程的效率太慢了,所以出现了线程,也把线程称为"轻量级进程",创建/摧毁线程的开销比进程小;
线程是被包含在进程里面的,一个进程可以有多个线程,每一个线程都是一个"执行流"可以单独在cpu上进行调度,同一个进程里面的线程,共用同一份系统资源(内存+文件)

那为什么引入多线程而不只是进程:
1.能够重复利用多核copu,提高效率
2.只是创建第一个线程的时候,需要申请资源,后续再创建新的线程,都是共用同一份资源(节省申请资源的开销).销毁线程时,也只需要销毁到在最后一个才真正释放资源,前面销毁的时候不需要释放资源

所以需要更改pcb的概念
操作系统内核,是通过pcb来描述的
更准确来说,是一组pcb(多个,至少一个)来描述一个进程,每个pcb对应一个线程
这一组pcb上的内存指针,文件描述符就是同一份东西.而状态,上下文,优先级,记账信息这些进程调度的问题,则是每一个pcb(线程)都有自己的一份.
所以说进程是资源分配的基本单位,线程是执行调度的基本单位

综上 进程和线程的区别?

  • 进程和线程的区别和联系
    1. 进程是包括线程的,一个进程里面可以有多个线程,线程比进程更轻量更快,创建/销毁更快
    2. 同一个进程的多个线程共用一块虚拟地址空间(内存/文件资源),会造成影响,产生安全问题,但是进程与进程之间,则不能造成影响,则是独立的虚拟空间地址
    3. 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位
  • 进程调度是以线程为单位进行调度(以PCB为单位进行调度)

需要注意的几点:

  1. 历史上,操作系统把一个进程的能够创建的线程数限制在一个相对比较少的数量上,因为数量过多可能会造成效率降低;
  2. 由于某个线程的异常,可能导致整个进程崩溃
  3. 当不同线程交替占有运行时(并发危险之竞争条件),虽然规约有规约描述,但者取决于实际运行时如何交替进行这些操作.
    因为线程共享相同的内存地址,且并发的运行,他们可能访问或者修改其他线程正在使用的同一个变量.(这是非常方便的,因为他可以更加简单的进行数据分享),因为给顺序编程模型加入了一些非顺序因素,这可能会造成混乱.所以java提供了同步机制来协调访问共享的变量,来使多线程程序行为可预见.

创建线程入门

线程的一些操作,在操作系统中,提供了一系列的api(Java将他们封装成Java风格的了)

class MyThread extends Thread{
    @Override
    public void run(){
        while(true){
            System.out.println("Thread");
            try {
                Thread.sleep(1000);
                //参数是休眠时间,单位ms;sleep表示"休眠",让线程阻塞一段时间
                //受查异常,需要手动处理,throw不行,因为此时在重写的run方法内
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
public class test1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            System.out.println("main");
            Thread.sleep(1000);
            //这里的就可以throws
        }
    }
}

MyThread创建的线程(run是这个新线程的入口方法):
标准库中提供了一个Thread方法,使用的时候就可以继承这个类,Thread方法相当于是对操作系统中的线程进行封装
重写run方法,run是Thread类父类中已经写好的方法,但是这里要重写一下,run里面的逻辑,就是这个线程要进行的工作.
main线程(jvm启动创建的,相当于线程的入口):里面创建MyThread实例,创建实例,并不会真的创建出来一个线程,只有在回调start方法,才真正创建出来一个线程.该线程会执行run里面的任务,一直到run里面的代码执行完了,新的线程就运行结束了.

运行一次java程序,就是启动了一个进程,一个进程里面至少有一个线程,就是main方法所在的线程(也叫做主线程).
main线程和MyThread创建出来的新线程,是一个"并发执行"(并发+并行)的关系,打开jconsole可以查看线程.

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值