进程&线程&多线程(详解)

本文详细阐述了进程、进程控制块(PCB)的概念,探讨了进程与线程的区别,包括它们的创建、调度、状态和资源管理。重点讲解了线程的启动、中断、join操作以及多线程编程在Java中的实践。
摘要由CSDN通过智能技术生成

目录

 

进程:

进程在系统中如何进行管理?

进程控制块(PCB):系统中专门描述进程属性的结构体

PCB中的属性:

线程Thread

进程和线程的区别(经典面试题)

多线程

线程启动——start方法

中断线程interrupt

线程等待join()

线程中还有一些其他状态:


进程:

一个跑起来的程序,就叫进程(程序运行时才算做进程)

每个进程,都是系统资源分配的基本单位

进程在系统中如何进行管理?

管理考虑两个角度:

1.描述 使用类/结构体,把被管理的一个对象,哥哥属性都表示出来

2.组织 使用数据结构,把这些表示出来的对象,串起来(为了后续的增删改查)

进程控制块(PCB):系统中专门描述进程属性的结构体

一个进程就可以使用一个或者多个PCB来表示

系统中会使用类似于双向链表这样的数据结构来组织多个PCB

(创建新的进程,就是创建PCB并且把PCB插入链表中;销毁进程,就是把PCB从链表中删除并释放;展示进程列表,就相当于是遍历链表的每个节点)

PCB中的属性:

1.pid进程的身份标识

每个进程都会有一个pid,同一时刻,不同进程之间的pid是不同的

2.内存指针(进程持有的内存资源)

进程在运行的时候,会分配一定的内存空间,内存指针区分内存空间具体是在哪里,以及分配的内存空间中有哪些部分,每个部分是干啥的

3文件描述符表(进程持有的硬盘资源)

(与文件有关->硬盘有关)

一个进程也需要涉及到硬盘操作,就需要按照文件的方式来操作

当前进程关联了哪些文件,都能操作哪些文件,都是通过文件描述表

PCB中引入了一些属性,用来支持操作系统实现进程调度的效果

4.进程的状态:

就绪状态①进程正在cpu上执行②进程时刻准备着去cpu执行

阻塞状态:进程某种执行条件不具备,暂时无法参与cpu的调度

等等其他状态,这里不做过多讨论

5.进程的优先级

优先级高的进程,操作系统会优先调度

(如游戏和qq同时运行,会先调度游戏进程 实现技能等 而qq接收消息等会放在后面)

6.上下文

进程从cpu离开之前,需要保存现场,把当前cpu中各种寄存器的状态,都记录在内存中,下次进程回到cpu上执行时,会沿着上次执行的位置继续执行(存档 读档)

7.记账信息

通过优先级机制,对不同的进程分配了不同的权重资源

并行:两个进程同时在两个cpu核心上执行

并发:一个cpu核心通过快速轮转调度执行多个进程

并行和并发在应用程序这一层感知不到,是系统内部完成调度(程序猿也不用区分,平时也会用并发来代指并行和并发)

cpu中的寄存器:

1.程序计数器(保存当前执行到哪个指令)

保存程序下一条执行的指令所在内存地址

2.维护栈相关的寄存器

栈,也是一块内存,保存当前这个程序调用过程中,一系列的关系,也包括局部变量和方法参数

3.其他通用寄存器

保存计算的中间结果

(10+20+30+40 假设算完10+20后进程调度走了,就需要把保存10+20的寄存器的值备份到上下文)

虚拟地址空间

是操作系统对内存又进行一层抽象(不是直接分配物理内存了,而是分配虚拟的内存空间)

通过上述方式,把进程之间给隔离开了,如果需要让多个进程相互配合,此时就不好搞了

为此,引入新的机制,来实现进程之间的通信

1.通过文件

2.网络(socket)


线程Thread

java中并不是鼓励使用多进程编程,更鼓励使用多线程编程

多进程有明显缺点:

进程太重量,效率也不高

创建、销毁、调度一个进程,消耗时间多(消耗在申请资源上)

为了解决上述问题,就引入了线程(也叫轻量级进程)

创建、销毁、调度线程,都比进程快

线程不能独立存在,要依附于进程,进程可以包含一个或多个线程(每个线程都可以独立执行一些代码)

进程类比于剧组,线程类比于演员,cpu核心类比于舞台

每个线程,都是可以独立进行调度的,每个线程都有状态、优先级、上下文、记账信息...

一个进程用一个或多个PCB表示,每个PCB对应到一个线程上

线程特点:

1.每个线程都可以独立的去cpu上调度

2.同一个进程的多个线程之间,共用同一份内存空间、文件资源...(创建线程的时候不需要重新申请资源了,直接复用之前分配给进程的资源,省去资源分配的开销)

进程中包含线程 =>一个进程由多个 PCB 共同表示 =>每个 PCB 就用来表示一个线程 =>每个线程都有自己的 状态,上下文,优先级,记账信息 =>每个线程都可以独立的去 CPU 上调度执行 =>这些 PCB 共用了同样的内存指针和文件描述符表 =>创建线程(PCB) 不需要重新申请资源 =>创建/销毁效率都更高了

进程是资源分配的基本单位

线程是调度的基本单位


进程和线程的区别(经典面试题)

1.进程包含线程,一个进程里可以有一个线程,也可以有多个线程

2.进程和线程,都是用来实现并发编程场景的,但是线程比进程更轻量,更高效

3.同一个进程的线程之间,共用同一份资源(内存+硬盘),省去了申请资源的开销

4.进程和进程之间具有独立性,一个进程挂了,不会影响别人

(同一个进程中,线程和线程可能会相互影响 线程安全+线程出现异常)

5.进程是资源分配的基本单位,线程是调度的基本单位


java如何进行多线程编程?

通过Thread类创建出一个线程

Thread再在java.lang这个包下(不用import)

线程入口方法:一个线程跑起来,是从哪个代码开始执行的

每个进程中至少会有一个线程(主线程),主线程的入口方法时main方法

class MyThread extends Thread{
   @Override
   public void run(){
     System.out.println("hello thread");
    }
}

(注意:这只是定义,想要执行 还要调用)

多线程

创建线程:

①继承Thread,重写run

②实现Runnable,重写run

③继承Thread,重写run,使用匿名内部类

④实现Runnable,重写run,使用匿名内部类

使用lambda表达式

所有前台线程结束,程序结束。后台只能服从前台, 当前台线程全部结束,后台将强制全部退出。

Thread对象生命周期往往比系统中的线程要更长。线程没了,Thread对象还在

线程启动——start方法

start方法内部,是会调用到系统api,来在系统内核中创建线程

(run方法 只是单纯的描述了该线程要执行什么 run方法会在创建好线程后自动被调用)

面试题:start和run的区别

本质的差别在于是否在系统内部创建出新的线程

中断线程interrupt

在java中 做法就是想办法让run方法尽快执行结束

1.在代码中手动创建出标志位,来作为run执行结束的条件

public class Main {
    private static boolean isQuit=false;
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            while(!isQuit){
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程结束工作");
        });
        t.start();
    }
}

注意:isQuit不能是局部变量,lambda表达式会变量捕获,自动捕获上层作用域中涉及的局部变量(在lambda表达式中复制了一份 此时外部的改变与lambda表达式中无关了)

而设为成员变量后 是“内部类访问外部类” 不是复制一份了~

上述方法不足:

①需手动创建变量

②当线程内部在sleep时,主线程修改变量。新线程内部不能及时响应

2.

t.interrupt();

 interrupt使sleep内部出发一个异常,从而被提前唤醒

但是,异常确实出现了,sleep唤醒了,但t仍然在工作,并没有正在结束

(原因:interrupt唤醒线程后,sleep方法抛出异常,同时自动清除刚刚设置的标志位,使得没有生效)

解决方法:

Thread t = new Thread(() -> {
            while (true) {
                System.out.println("t线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //做一些其他工作,完成之后再结束(让我们有更多可操作空间)
                    System.out.println("我要结束了");
                    //结束线程
                    break;
                }
            }
            //System.out.println("线程工作完毕!");
        });

线程等待join()

t.join() 主线程中调用t.join()就是让主线程等待t线程先结束

①如果t线程正在运行中,此时调用join的线程就会阻塞,一直到t线程执行结束为止

②如果t线程已经执行结束了,此时调用join线程,就直接返回了,不会涉及到阻塞

public void join (long millis)//指定等待时间

sleep(long millis,int nanos)

微秒意义不大 本身存在一定精度误差

时间到了之后系统会唤醒这个线程,但并不是立即阻塞->就绪(中间有一个调度开销)

public class myself2 {

    public static void main(String args[]) throws InterruptedException {
        Thread t1=new Thread(()->{
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t1结束");
        });

        Thread t2=new Thread(()->{
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t2结束");
        });
        t2.start();
        t1.start();
        System.out.println("主线程结束");
    }
}

输出:

主线程结束
t1结束
t2结束

线程中还有一些其他状态:

NEW:Thread对象已经有了,start方法还没调用

TERMINATED:Thread对象还在,内核中的线程已经没了

RUNNABLE:就绪状态(线程已经在cpu上执行了/线程正在排队等待cpu上执行)

TIMED_WAITING:阻塞。由于sleep这种固定时间的方式产生的阻塞

WAITING:阻塞。由于wait这种不固定时间的方式产生的阻塞

BLOCKED:阻塞,由于锁竞争导致阻塞

(三种不同原因 方便后续定位线程卡死原因)

多线程基础:

线程的基本知识点、操作方式 和面试、实际工作都是密切相关的

多线程进阶:

和面试关系大,实际工作中不太用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值