当前位置: 首页 > article >正文

多线程---认识线程

文章目录

  • 什么是进程?
    • 如何管理进程?
    • 认识PCB
    • 了解进程调度的过程
    • 虚拟地址空间
  • 什么是线程?
  • 进程 VS 线程
  • Thread类的属性和方法
    • Thread类的属性
    • Thread类的方法
      • 构造方法
      • 普通方法
  • 线程的状态

什么是进程?

进程,也叫做“任务”,一个跑起来的程序就是进程。也就是说进程是运行起来的程序。在同一时刻,操作系统中的进程有很多,他们是如何管理的呢?

如何管理进程?

管理进程实际上就是做两件事儿:

  1. 描述进程:详细的表示一个进程有哪些属性、哪些信息?这是通过一个结构体来实现的,这个结构体里面包含了进程的各种信息,这个结构体叫做PCB(进程控制块);
  2. 组织进程:通过一个数据结构把若干个PCB联系起来,使其可以进行增查改删。这个数据结构一般使用双向链表。

注:

  1. 创建进程实际上就是创建一个PCB,然后把它放到双向链表中。
  2. 结束进程实际上就是在双向链表中找到这个节点,然后把这个节点删除。
  3. 查看进程列表实际上是遍历整个双向链表。
  4. 一个进程可能是一个PCB,也可能是多个。

认识PCB

认识PCB,就是了解PCB里面到底包含了哪些信息:

  • pid:是一个进程的身份标识

    在同一台主机,同一时刻,这些进程的pid是唯一的。通过pid来区分进程。

  • 内存指针:描述进程持有的内存资源

    当我们双击一个可执行文件时,操作系统就要把这个文件的核心数据加载到内存中,同时会在内存中创建进程PCB。这就会给进程分配一定的内存空间,这个内存空间会被分为不同的区域,内存指针就是来描述每个区域是干嘛的。

  • 文件描述符表:描述进程持有的文件资源

    每个进程都可以打开一些文件(存储在硬盘上的数据),文件描述符表里就记录了当前这个进程打开了哪些文件。

  • 进程状态:描述进程当前能否被调用

    就绪状态:进程可以被调度到CPU上执行。
    阻塞状态:进程不能被调度到CPU上执行。

  • 进程优先级:描述进程调用的先后顺序

    在创建进程时,可以通过一些系统调用来干预优先级。

  • 进程上下文:保存当前进程执行过程中产生的中间结果。

    一个进程在CPU执行一会儿之后,会切换到另一个进程执行,在过一段时间之后可能会再次切换回来继续执行。那么此时就需要知道上次执行到哪儿了。进程上下文就是用来保存中间结果的。

  • 进程记账信息:统计一个进程在CPU上执行了多久

    进程在执行时由进程优先级控制执行哪个进程,但是这样就有可能导致某个进程一直执行不到。通过统计进程记账信息,能让进程调度更均衡,避免执行不到某个进程。

了解进程调度的过程

其中,进程状态、进程优先级、进程上下文和进程记账信息都是和进程调度相关的信息,那么什么是进程调度呢?

我们先要明白: 进程是操作系统进行资源分配的基本单位

进程调度其实是由“并行” + “并发”的方式执行的。

并行,即:在每个CPU核心上都可以独立的运行一个进程,多个CPU核心就可以同时运行多个进程。

并发,即:在一个CPU核心上,先运行一下进程1,再运行一下进程2,再运行一下进程3,再运行一下进程1…这样循环执行。只要切换的速度足够快,宏观上看起来三个进程就是在同时运行。

进程状态、进程优先级、进程上下文和进程记账信息存在的意义,就是支撑“进程调度”

虚拟地址空间

虚拟地址空间也是进程中非常关键的概念。

我们知道在创建进程时,都会给每个进程分配一定的内存空间,用来完成进程的工作。即:
在这里插入图片描述

在正常情况下,进程各自使用各自的内存,不会有任何问题。但是如果某个进程使用了野指针,不小心访问到了别的进程的内存且进行了修改。这就是个大问题:它这样做不仅仅影响到了自己的执行,而且还影响到了别人的执行。我们就通过虚拟地址空间来避免这个问题。

在这里插入图片描述

我们通过“虚拟地址空间”让每个进程都拥有自己的内存空间,并且和其他进程的内存空间隔离开。当进程要访问内存时通过MMU设备进行虚拟内存空间到真正内存空间的映射,访问真正的内存。如果发现有进程访问的内存越界,MMU设备就会进行拦截,关闭此进程,不让它影响到其他进程。

面对有些需要让多个进程配合的场景,又引入了进程间通信机制。它的原理就是:找到一块所有进程都能访问的公共资源,然后基于公共资源来交换数据。

什么是线程?

虽然多进程已经实现了并发编程,但是有一个巨大的问题:如果频繁的创建进程、销毁进程,那么这个操作就比较低效。

创建进程的过程:1. 创建PCB 2.给进程分配资源并赋值到PCB中 3. 把PCB插入链表
销毁进程的过程:1. 把PCB从链表上删除 2. 把PCB持有的资源释放 3. 销毁PCB

其中,分配资源和释放资源对操作系统来说要做的工作非常多,需要花费大量的时间。

因此,程序员就发明了“线程”。一个进程默认至少有一个线程,也可能有多个线程。这些线程都可以单独的在CPU上进行调度。最重要的是:同一个进程中的这些线程共用同一份系统资源(内存+文件),创建线程和销毁线程的开销远小于进程。所以,也把线程称为“轻量级进程”。

前面提到操作系统是通过PCB来描述进程的,更准确的说法是通过一组PCB来描述进程的。
每一个PCB对应一个线程,而一个进程可能包含多个线程。

使用多线程有一些优势:

  1. 能够充分利用多核CPU,提高效率。
  2. 只有创建第一个线程的时候需要申请资源,后续再创建新的线程都是共用同一份资源,节省了申请资源的开销;销毁线程的时候,也只有销毁到最后一个线程的时候才释放资源,节省了释放资源的开销。

使用多线程也有一些问题:

  1. 线程数目不是越多越好。当CPU核心已经饱和时,继续增加线程不会提高效率。反而会因为线程太多,线程的调度开销太大,影响了效率。
  2. 线程之间可能会相互影响到,造成线程安全问题
  3. 如果某个线程发生了意外就可能让整个进程奔溃

进程 VS 线程

  1. 进程包含线程
  2. 线程比进程更轻量,创建更快,销毁也更快
  3. 同一个进程的多个线程共用同一份系统资源(内存+文件),进程和进程之间则是有各自的系统资源(内存+文件)
  4. 进程是资源分配的基本单位,线程是调度执行的基本单位

Thread类的属性和方法

Thread类的属性

在这里插入图片描述

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println(thread.getId());
        System.out.println(thread.getName());
        System.out.println(thread.getState());
        System.out.println(thread.getPriority());
        System.out.println(thread.isDaemon());
        System.out.println(thread.isAlive());
        System.out.println(thread.isInterrupted());

//        20
//        Thread-0
//        NEW
//        5
//        false
//        false
//        false

    }
}
  1. id是线程的唯一标识,不同的线程id不会重复。
  2. name在自己调试的时候会用到,可以自己在线程的构造方法里定义。
  3. state表示线程现在的状态(在下面介绍)
  4. priority在线程调度的时候会使用
  5. daemon:守护线程,也叫后台线程。前台进程:会阻止进程的退出,如果main线程执行完后,前台线程还没执行完,会等待前台线程执行完再退出进程;后台进程:不会组织进程的退出,当main线程执行完就退出进程。 我们创建的线程默认是前台线程。
  6. alive: 判断内核线程在不在。当new 出Thread对象但没有使用start方法启动时,不会把线程放入内核,使用start方法后才会把线程放入内核执行;当线程在内核执行完任务后,就会退出内核,清除内核线程,但是Thread对象还在。
  7. interrupt:线程中断,让线程提前结束,本质是让run方法尽快结束,不是让run方法执行到一半就退出。interrupt有两种情况:1. 如果线程正在执行 则设置标记位为true中断线程 2. 如果线程被阻塞 则唤醒sleep抛出异常 被catch捕获后在catch里处理,有两种中断的方式。
  • 使用线程库里面自带的标记位
    //通过使用标准库里自带的标记位
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // e.printStackTrace();

                    // [方式一] 立即结束线程
                    break;

                    // [方式二] 啥都不做, 不做理会. 线程继续执行

                    // [方式三] 线程稍后处理
                    // Thread.sleep(1000);
                    // break;
                }
            }
            System.out.println("t 线程执行完了");
        });

        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t.interrupt();
        System.out.println("设置让 t 线程结束!");
    }
  • 自定义一个标记位
//自己设置一个标记位  用来中断退出
    public static boolean isQuit = false;

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!isQuit){
                System.out.println("执行线程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        while (true){
            System.out.println("执行main");
            isQuit = true;
            System.out.println("手动中断线程");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

Thread类的方法

构造方法

在这里插入图片描述
构造方法主要是用来创建线程的,现在共有七种创建线程的方式,点击查看

在构造方法里可以添加一个String类型的参数,用来命名线程。

普通方法

  • start():启动线程。把线程放到内核中执行。

  • interrupt():中断线程。让线程提前退出。

  • join():线程等待。在main中调用join(),就是等待该线程执行完了再执行main线程。

  • sleep():线程休眠。让线程阻塞一段时间。

    PCB在管理线程时有俩个队列:一个就绪队列、一个阻塞队列。调用sleep()就是把线程放到阻塞队列里,等阻塞时间结束再放回就绪队列参与调度。

线程的状态

在这里插入图片描述

线程一共有六大状态,我们可以这样理解:
在这里插入图片描述


http://www.kler.cn/a/106218.html

相关文章:

  • Go 语言切片初始化与性能优化:使用 cap 参数的重要性
  • uniapp微信小程序接入airkiss插件进行WIFI配网
  • 什么是 C++ 内联函数?它的作用是什么?
  • ARM(安谋) China处理器
  • ReactPress与WordPress:两大开源发布平台的对比与选择
  • Cartographer激光雷达slam -20241116
  • 使用 Visual Studio Code 编写 TypeScript程序
  • 内存-虚拟地址到物理内存地址转换
  • 【数据结构初阶】顺序表和链表(1)
  • fl studio2023最新版本如何设置中文?
  • 多线程线程池
  • Git总结
  • 使用 jdbc 技术升级水果库存系统(后端最终版本,不包含前端)
  • ubuntu server 安装失败
  • ​iOS安全加固方法及实现
  • kubernates 集群实战-安装K3s集群
  • pdf误删恢复如何恢复?分享4种恢复方法!
  • 【vue3 】 创建项目vscode 提示无法找到模块
  • vue实现连接线
  • 23种设计模式【创建型模式】详细介绍之【建造者模式】
  • 软考系统架构师知识点集锦四:信息安全技术基础知识
  • OpenCV学习(三)——响应鼠标事件(获取点击点坐标和颜色,利用鼠标进行绘图)
  • 【CSDN Daily Practice】【二分】X的平方根
  • docker基础镜像定制
  • 在 Visual Studio Code (VS Code) 中设置
  • 如何进行微服务测试?一文4个知识点带入门微服务测试!