进程、线程的创建方法及 Thread 类的基本用法

本文详细介绍了操作系统中进程的管理,包括进程控制块(PCB)、进程调度和通信,以及线程的概念、优势和Java中的实现。通过分析进程与线程的区别,展示了线程在并发执行中的重要作用,并列举了Java中创建线程的多种方法,如继承Thread类、实现Runnable接口等。最后,探讨了Thread类的常用方法,如start()、interrupt()和join(),以及线程的中断与等待机制。
摘要由CSDN通过智能技术生成


CPU:本质上是通过一些基础的门电路构成的:
电子开关=>基础门电路=>半加器=>全加器=>加法器=>进行加减乘除
CPU跑的快主要是因为当前这个CPU集成程度非常高.里面包含了非常多的门电路。


一、进程

操作系统是一个管理的软件。
对下,要管理好各种硬件设备
对上,操作系统要给各种软件提供稳定的运行环境
操作系统中提供的功能,是非常复杂的,这里重点介绍操作系统中针对"进程"这个软件资源的管理。

进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位
进程(process)又叫任务(task)。

1.1 操作系统是如何管理进程的?(进程是如何管理的)

  1. 先描述一个进程(明确出一个进程上的一些相关属性)

操作系统里面主要都是通过C/C++来实现的.此处的描述其实就是用的C语言中的"结构体"(也就和Java的类差不多)。操作系统中描述进程的这个结构体,称为"PCB" (process control block 进程控制块)。

  1. 再组织若干个进程(使用一些数据结构,把很多描述进程的信息放在一起,方便进行增删改查)

典型的实现就是使用一个双向链表,把每个进程的PCB给串起来。操作系统的种类是很多,内部的实现也是各有不同,此处所讨论的情况是以Linux这个系统为例。

所谓的“创建进程",就是先创建出PCB,然后把PCB加到双向链表中;
所谓的"销毁进程",就是找到链表上的PCB,并且从链表上删除;
所谓的“查看任务管理器"就是遍历链表。

1.2 PCB(进程控制块)中的一些属性

  1. pid(进程id):进程的身份标识—进程的身份证号;

  2. 内存指针:指明了进程要执行的指令/代码在内存哪里,以及进程中依赖的数据都在哪里。当运行一个exe,此时操作系统就会把这个exe加载到内存中,变成进程.

  3. 文件描述符表:程序运行过程中经常要和文件打交道(文件是在硬盘上),进程每次打开一个文件,就会在文件描述符表上多增加一项(这个文件描述符表就可以视为一个数组,里面的每个元素又是一个结构体,就对应一个文件的相关信息)。
    一个进程只要一启动,不管代码中是否写了打开/操作文件的代码,都会默认的打开三个文件(系统自动打开的):标准输入(System.in),标准输出(System.out),标准错误(System.err);这个文件描述符表的下标,就称为文件描述符。

1.3 进程调度的基本过程

上面的属性是一些基础的属性.下面的一组属性,主要是为了能够实现进程调度

状态:这个状态就描述了当前这个进程接下来应该怎么调度
就绪状态:随时可以去CPU上执行.
阻塞状态/睡眠状态:暂时不可以去CPU上执行.(Linux中的进程状态还有很多其他的…)

优先级:先给谁分配时间,后给谁分配时间,以及给谁分的多,给谁分的少

记账信息:统计每个进程,都分别被执行了多久,分别都执行了哪些指令,分别都排队等了多久;给进程调度提供指导依据的。

上下文:表示了上次进程被调度出CPU的时候,当时程序的执行状态,下次进程上CPU的时候,就可以恢复之前的状态,然后继续往下执行。
进程被调度出CPU之前,要先把CPU中的所有的寄存器中的数据都给保存到内存中(PCB的上下文字段中)相当于存档了.下次进程再被调度上CPU的时候,就可以从刚才的内存中恢复这些数据到寄存器中.相当于读档了.
存档+读档~~ 存档存储的游戏信息,就称为"上下文"。

1.4 进程间通信

进程的调度,其实就是操作系统在考虑CPU资源如何给各个进程分配
内存资源又是如何分配的呢?通过虚拟地址空间
由于操作系统上,同时运行着很多个进程,如果某个进程,出现了bug,进程崩溃了,是否会影响到其他进程呢?现代的操作系统(windows, linux, mac…)能够做到这一点,就是“进程的独立性"来保证的,就依仗了"虚拟地址空间"。

进程之间现在通过虚拟地址空间,已经各自隔离开了,但是在实际工作中,进程之间有的时候还是需要相互交互的。这就是进程间通信

操作系统中提供了多种这样的进程间通信机制(有些机制是属于历史遗留的,已经不适合于现代的程序开发)
现在最主要使用的进程间通信方式,两种:
1.文件操作
2.网络操作(socket)

二、线程

1.线程是什么?

一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码。
例如:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。

2.为什么要有线程?

我们的系统支持多任务了,程序猿也就需要"并发编程",通过多进程,是完全可以实现并发编程的,但是如果需要频繁的创建/销毁进程,这个事情成本是比较高的.如果需要频繁的调度进程,这个事情成本也是比较高的
解决上述这个问题的方法:

1.进程池(数据库连接池.字符串常量池)
进程池虽然能解决上述问题,提高效率,同时也有问题.池子里的闲置进程,不使用的时候也在消耗系统资源.消耗的系统资源太多。

2.使用线程来实现并发编程,线程比进程更轻量.每个进程可以执行一个任务.每个线程也能执行一个任务(执行一段代码),也能够并发编程.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.

3. 为啥线程比进程更轻量?(进程重量是重在哪里?)

重在资源申请释放(在仓库里找东西…)
线程是包含在进程中的.一个进程中的多个线程,共用同一份资源(同一份内存+文件)
只是创建进程的第一个线程的时候(由于要分配资源)成本是相对高的,后续这个进程中再创建其他线程,这个时候成本都是要更低一些,不必再分配资源。

4. (重点)线程与进程的区别?

  1. 进程包含线程,一个进程里面可以有一个线程,也可以有多个线程
  2. 进程和线程都是为了处理并发编程这样的场景,但是进程在频繁创建和销毁中,开销更高.线程开销更低.(线程比进程更轻量)
  3. 进程是系统分配资源(内存,文件资源…)基本单位.线程是系统调度执行的基本单位(CPU)。
  4. 进程具有独立性,每个进程有各自独立的独立的虚拟地址空间,一个进程挂了,不会影响其他进程。同一个进程有多个线程,共用同一块内存空间,一个线程挂了,可能影响到其他线程。

5. Java 的线程和操作系统线程的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

线程之间是并发执行的

在一个进程中,至少会有一个线程.在一个java进程中,也是至少会有一个调用main方法的线程(这个线程不是你手动搞出来的)
如下面例子中,自己创建的thread 线程和自动创建的main线程,就是并发执行的关系(宏观上看起来是同时执行)。

class MyThread2 extends Thread{
   
    @Override
    public void run() {
   
        //run方法中得逻辑,是在新创建出来的线程中被执行的代码
        while (true){
   
            System.out.println("hello thread");
            try {
   
                //这个休眠操作,就是强制的让线程进入阻塞状态,单位是ms 1s之内这个线程不会到cpu上执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}
public class Test02 {
   
    public static void main(String[] args) {
   
        Thread thread = new MyThread2();
        thread.start();

        while (true){
   
            //一个进程中,至少会有一个线程
            System.out.println("hello main");
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }
    }
}

执行结果:
在这里插入图片描述

现在两个线程,都是打印一条,就休眠个1s,每一轮1s时间到了之后,到底是先唤醒main还是 thread,这是不确定的(随机的)。
对操作系统来说,内部对于线程之间的调度顺序,在宏观上可以认为是随机的(抢占式执行)。这个随机性,会给多线程编程带来很多麻烦。

6. 创建线程的5种写法

方法1 继承 Thread 类

创建子类,继承自Thread,并且重写run方法.

run方法中描述了这个线程内部要执行哪些代码.每个线程都是并发执行的. (各自执行各自的代码)因此就需要告知这个线程,你执行的代码是啥。并不是我一定义这个类,一写run方法,线程就创建出来,需要调用start方法,才是真正的在系统中创建了线程,才是真正开始执行上面的run操作。在调用start之前,系统中是没有创建出线程的。

class MyThread extends Thread{
   
    @Override
    public void run() {
   
        //run方法中得逻辑,是在新创建出来的线程中被执行的代码
        System.out.println("hello");
    }
}
public class Test01 {
   
    public static void main(String[] args) {
   
        Thread t = new MyThread();
        //执行到这里,才真正在系统中创建了线程
        t.start();
    }
}

方法2 实现 Runnable 接口

创建一个类,实现Runnable接口,再创建Runnable实例传给Thread实例。

//通过Runnable来描述任务的内容,然后进一步再把描述好的任务交给Thread
class MyRunnable implements Runnable{
   
    @Override
    public void run() {
        
        System.out.println("hello");
    }
}
  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值