目录
进程:
一个跑起来的程序,就叫进程(程序运行时才算做进程)
每个进程,都是系统资源分配的基本单位
进程在系统中如何进行管理?
管理考虑两个角度:
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:阻塞,由于锁竞争导致阻塞
(三种不同原因 方便后续定位线程卡死原因)
多线程基础:
线程的基本知识点、操作方式 和面试、实际工作都是密切相关的
多线程进阶:
和面试关系大,实际工作中不太用