JavaEE 进程&线程
文章目录
- JavaEE 进程&线程
- 1. 进程
- 1.1 概念
- 1.2 进程管理
- 1.3 PCB (Process Control Block)
- 2. 线程
- 2.1 概念
- 2.1 线程与进程的区别
- 2.3 创建线程
1. 进程
1.1 概念
什么是进程?
进程是操作系统对一个正在执行的程序的一种抽象
我们可以打开任务管理器,里面每个正在执行的程序就是一个进程:
注: 我们可以认为,程序 = 进程 (跑起来的程序) / 可执行文件(没有运行起来的程序)
而对于进程来说,它们在执行的过程中都需要消耗一定的硬件资源
结论:
-
进程是系统分配资源的基本单位
-
内存管理:每个进程的内存是彼此独立,互不干扰的。通常情况下,进程A不能直接访问进程B的内存,这种情况也称为进程独立性
-
进程间通信:虽然有进程的独立性,但有时候也需要多个进程相互配合完成某个工作,这个时候就要用到进程间通信。进程间通信和进程的“独立性”并不冲突,系统提供一些公共的空间(多个进程都能访问到的),让两个进程借助这种公共空间来交互数据
1.2 进程管理
在操作系统中,进程管理可以分为两个步骤:
- 先描述: 使用
PCB
将核心属性描述出来 - 再组织: 使用一定的数据结构(在Linux中一般使用链表),把对应进程的
PCB
串到一起
下面将对PCB进行具体解释~
1.3 PCB (Process Control Block)
PCB 是指操作系统中的进程控制块(Process Control Block
),也称为任务控制块(Task Control Block
)。每个正在运行或等待执行的进程都有一个对应的 PCB。
进程的一些重要信息就存储在PCB中:
-
PID(Process ID):进程的身份标识,系统会保证同一个机器上,在同一时刻每个进程的
pid
都是唯一的; -
内存指针(Memory Pointers):描述进程使用内存资源的详细情况,如哪里存放数据,哪里存放指令;
-
文件描述符表(File descriptor table):描述进程使用了的硬盘的相关信息;
-
优先级(Process Priority):表示进程的相对重要性或优先级。操作系统可以根据进程的优先级来进行调度,以决定哪个进程先执行;
-
进程状态(Process State):记录进程当前的状态,如运行、就绪,阻塞(等待控制台输入)等。这是操作系统用于判断进程是否可以执行的关键信息;
-
进程调度(Process Scheduling):针对每个进程占据了cpu多长时间进行一个统计,根据这个统计结果来进一步的调整调度的策略;
-
上下文(Context):类似于存档与读档。其中保存上下文就是把CPU的关键寄存器中的数据保存到内存中(PCB的上下文属性中),而恢复上下文就是把内存中的关键寄存器中的数据加载到CPU的对应寄存器中
PCB扮演着操作系统中的重要角色,通过保存和维护进程的状态和上下文信息,操作系统可以在进程之间进行切换和调度!!
2. 线程
引子:进程的使用能够解决“并发编程”的问题,但进程本身在进行频繁创建和销毁的时候,开销也比较大(开销主要体现在资源的申请和释放上),而线程
能够解决这个问题!
2.1 概念
什么是线程?
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间可以“同时”执行多份代码
线程也可以称为“轻量级进程”,在进程的基础上做出了改进,同时,线程在系统中的调度规则和进程是一样的,线程的PCB中也有状态、优先级、上下文、记账信息…
2.1 线程与进程的区别
-
进程是包含线程的,每个进程至少有一个线程存在,即主线程;
-
进程和进程之间不共享内存空间,而线程与线程之间共享一个内存空间;
多个线程的PCB中的内存指针都指向同一个内存空间,这就意味着只有在创建第一个线程的时候需要从系统中分配资源,后续的线程就不必分配,直接共用前面分配的资源就可以,这大大降低了资源频繁申请释放带来的开销!
-
进程是系统分配资源的基本单位,线程是系统调度的基本单位;
-
进程与进程之间不会相互影响,一个进程挂了一般不会影响其它正在执行的进程,但线程不一样,一旦一个线程挂了很可能会把同个进程中的其它线程一起带走,导致整个进程一起崩溃;
-
因为共享同一个内存空间,同一个进程中的线程可能会相互干扰,从而造成线程安全问题;
-
线程也不是越多越好,线程太多调度开销也会很大
2.3 创建线程
创建线程有以下几种方式:
-
继承Thread类
-
先继承Thread来创建一个线程类
class MyThread extends Thread { @Override public void run() { System.out.println("这里是线程运行的代码"); } }
-
创建MyThread类的实例
Thread myThread = new MyThread();
-
调用start方法启动线程
myThread,start(); //线程开始运行
从上述代码可以发现,我们并没有用
对象.方法名
的方式调用run
方法,但却依旧能够执行run
方法,关键就在于:- 线程在调用start()方法的时候才正式构建
- run()方法相当于这个线程的入口方法,此处的run方法不需要我们手动调用,而会在线程创建好之后(即调用start方法后)被JVM自动调用执行
-
-
实现Runnable接口
-
实现Runnable接口
class MyRunnable implements Runnable { @Override public void run() { System.out.println("这是线程运行的代码"); } }
-
创建Thread类实例,调用Thread的构造方法时将Runnable对象作为target参数
Thread t = new Thread(new MyRunnable());
-
调用start方法
t.start(); //线程开始运行
-
-
匿名内部类创建Thread子类对象
package demo1; public class DemoThread3 { public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { System.out.println("使用匿名类创建Thread子类对象"); } }; t1.start(); } }
-
匿名内部类创建Runnable子类对象
package demo1; public class DemoThread5 { public static void main(String[] args) { Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名类创建Runnable子类对象"); } }); t2.start(); } }
-
使用lambada表达式创建Runnable子类对象
package demo1; public class DemoThread5 { public static void main(String[] args) { Thread t3 = new Thread(() -> { System.out.println("这里使用lambda表示式相当于实现Runnable接口并重写了run方法"); }); t3.start(); } }
因为lambda
表达式写起来比较简洁,所以我们一般使用lambda表达式来创建线程。