程序 : 指的是计算机中的一些exe可执行文件 (硬盘中)把程序跑起来 才会涉及到"进程"
进程 : 如果双击程序 此时操作系统就会把可执行文件中的数据和指令加载到内存中 将程序执行后运行中叫做程序 进程又叫做程序的实例化
进程与程序的关系
进程要运行 说明消耗一定的硬件资源
会消耗 cpu资源 内存 硬盘 网络宽带…
程序是静态 只占用了硬盘资源
假设下载了一个病毒没有安装是否会中病毒??
不会 下载的是一个程序 是静态的(没法执行任何操作)
安装则是真正运行起来成为进程了
进程是系统分配硬件资源的基本单位
针对计算机的进程的管理
描述: 会使用一个专门的结构体(双向链表)来记录一个进程里面的各个属性
PCB(进程控制块Processing Control Block)
查看进程的列表 本质上就是在遍历这个链表
创建一个进程 就是创建了一个pcb结构体 并插入到链表上
销毁一个进程 就是把pcb结构体从连表示删除并释放
PCB中大概有那些信息?
1. pid进程的标识
同一个系统上 同一时刻 每个进程的pid一定都是不同的
2. 内存指针
表示了该进程 对应的内存资源 是咋样的
内存资源中要存啥?
最主要的是存储的就是从exe可执行文件加载过来的指令和数据
3. 文件描述符表
和硬盘资源有关
银盘是硬件应用程序一般是无法直接接触到"硬件"这一层 实际上是操作系统抽象成"文件"这样的概念 程序操作文件 文件实际上是存储在硬盘上的 每一个进程就会有一个"文件描述符表"来记录 当前中国进程正在使用那些文件
组织: 会使用一系列的数据结构 把多个进程进行一个有效的组织 随时方便进行遍历 查找 汇总数据
在一个计算机上有许多进程 进程需要在cpu上执行指令
一台机器只有一个cpu 所以要想运行进程是轮流使用的
cup的调度
每个进程 都需要一定的内存资源
早期的操作系统 就是直接把物理内存分配给进程 就会带来一个严重的问题 一旦某个内部代码写出bug 内存越界访问了 就可能会影响到 别的进程
按照上诉 直接分配物理内存的模型 此时一旦指针越界指向其他位置 就很可能影响到别的进程的执行 后来操作系统就引用了虚拟地址空间
这样设定之后 每个进程的有效的虚拟地址 都是固定范围 进程使用该虚拟地址的内存 都是需要操作系统进行转换成物理地址的过程 中国转换过程中 就可以针对 虚拟地址是否有效 做出一个 校验
在虚拟地址空间的加持下 => 进程就具有了"独立性" 每个进程都有自己的虚拟地址空间 一个进程无法直接访问或者修改其他进程虚拟地址空间的内容 强化了系统的稳定性
通过虚拟地址空间 把进程隔离开了 但是有时候 还要让进程之间 产生点配合 进程间通信 就是在进程隔离性的基础上 开个口子 能够有限制的进行相互影响
多进程已经很好实现 并发编程的效果
但是进程有明显的缺点 : 进程太重了
轻量级 进程 => 线程(只创建了一个pcb 但是没有分配后续的内存硬盘…资源)
创建的还是进程 创建进程的时候 把资源分配好
后续创建线程 让线程在进程内部 (进程和线程之间的关系 可以认为是 进程包含了线程)
后续程序中新的线程 直接复用前面进程这里创建好的资源
其实一个进程 至少要包含一个线程 后面继续将建线程就可以省略分配资源的过程 资源已经是有了的
线程和进程的关系
例如
进程,是操作系统进行资源分配的基本单位
线程,是操作系统进行调度执行的基本单位
例如
要吃100只鸡
单进程模式
多进程模式
优点 效率提高
缺点 分配更多资源
多线程模式
创建线程 比创建进程更轻量
但是一个进程中能创建的线程是有限的
一个进程只能创建固定的线程(cpu的逻辑核心数)
一个线程在执行过程中出现异常 并且这个异常没有很好的被处理很有可能会导致这个进程直接中止而多进程没有这个顾虑 一个是更轻量 一个是更稳定
面试题(高频)
进程和线程的区别和联系
Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装
package thread;
class MyThread extends Thread{
@Override
//这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
public void run(){
System.out.println("hello world");
}
}
//Thread 类来自java.lang这个包 不需要import就可以直接用
public class Demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();//此处不仅仅要创建出类 还需要调用它让它执行起来
myThread.start();
//此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
//此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
}
}
Thread 类来自java.lang这个包 不需要import就可以直接用
这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
当我们这样写的时候
run只是上面的入口方法(普通方法) 并没有调用系统api 也没有创建出真正的线程来
当点击运行程序的时候就会先创建出一个java进程这个进程中就包含了至少一个线程 这个线程也叫做主线程 也就是负责执行main方法的线程
当我们调整代码之后 在main方法中有一个while循环 在线程中run也有一个while循环
使用start的方式执行 此时这俩循环都在执行
两个线程分别执行之间的循环 这两个线程都能参与cpu的调度中以并发式执行
如果将代码改为
它只创建了一个线程 只有当run方法执行完成之后才会执行下面的代码 所以输出结果只有 hello world
总结
我们可以使用 jconsole查看线程
可以在jdk的路径下寻找
然后我们启动java程序
选择
能够列出系统上正在运行的所有的java进程
线程的调用栈
线程的调用栈 方法之间的调用关系
尤其是 当程序卡死了 查看一下 这里每个线程的调用栈 就可以知道大概那个代码出现卡死情况
Tread.sleep
Tread.sleep 时间是毫秒
运行结果并非是严格的交替
这两线程在进行sleep之后就会进入阻塞状态
当时间到之后 系统就会唤醒这两线程 回复线程的调度
谁先调度 可以认为是"随机"的
这样"随机"的调度过程 称位"抢占式执行"
Java中 通过Thread 类创建线程的方式还要很多种写法
class MyRunnable implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
while (true){
System.out.println("hellw main");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
Thread 这里是直接把要完成的工作放到了Thread的run方法中
Runnable 这里 则分开了 要把完成的工作放到Runnable 中再让Runnable 和Thread配合