当前位置:网站首页>【进程和线程】
【进程和线程】
2022-06-23 11:28:00 【Word码鸭】
目录
用户态(user space) 和 内核态(kernel space)
进程通信IPC(Inter Process Communication)
(2)进程(process) 和线程(thread) 的关系
(4)获取当前线程引用——Thread.currentThread()
进程的五大状态
- 新建:进程处于正在创建中
- 就绪:万物具备,只欠CPU
- 运行:进程的指令真正在CPU运行着
- 阻塞:进程由于等待外部条件,所以暂时无法继续
- 结束:进程的所有指令执行结束,但PCB暂时保留,OS还需要做一些其他工作的时候
进程状态转移图:

所有运行状态必须是从就绪状态变过去的,因为进程的CPU必须经过OS系统分配才能得到。
进程状态如何转移
- ->新建 : 随着程序的启动运行
- 新建->就绪 : 进程的初始化工作完全完成,这个工作是由OS的指令完成的
- 就绪->运行 : 进程被OS选中,并分配了CPU之后
- 运行->结束 : 进程的最后一条指令执行结束,可粗略地理解为就是main方法执行结束了
- 运行->就绪 : 1.被高优先级的进程抢占了;2. 时间片耗尽;3. 进程可以执行一些OS提供的系统调用,主动放弃
- 运行->阻塞:等待一些外部条件,如等待I0设备;进程休眠一段时间等
- 阻塞->就绪 : 外部条件满足,如 I0数据来了、休眠时间到了...
- 结束-> : 进程PCB彻底被OS回收了
在同一时刻,处于不同状态下的进程不止一个。处于新建、就绪、堵塞、结束状态的可以有多个,但单核cpu情况下,同一时刻运行状态的只能有一个。
OS具体怎样进行进程切换
通过上下文切换:保护上一个进程的上下文 + 恢复下一个进程的上下文
上下文:以PC寄存器所代表的一组寄存器中的值。
保护上下文:把寄存器中的值,保存到内存的某个位置。 恢复上下文:把内存中之前保护的值,写入寄存器中
并行(parallel) 和 并发(concurrent)
并行:进程真的同时在执行,既微观角度的同一时刻,是有多个指令在执行的,所以只会在多CPU多核场景下
并发:进程假的同时在执行,微观上,表现为一次只执行一个进程,但宏观上,多个进程在“同时"执行
用户态(user space) 和 内核态(kernel space)
当CPU正在执行的是OS的指令时,就认为进入到内核态。反之,正在执行的是普通进程的指令时,就在用户态。 内核态的指令权限高,基本上所有的硬件都能访问;用户态的指令权限低,只能访问OS规定的资源。这是因为CPU本身就有权限开关。 一般来说,用户态的性能较好,内核态的性能较差。
执行流(executionflow)
各执行流拥有独立pc的一套指令,不同的执行流从现象上看起来是完全独立的。
内存管理
和cpu不同,cpu更注重在时间上的划分,而操作系统对内存资源的分配,采用的是空间模式 —— 不同进程使用内存中的不同区域,互相之间不会干扰。
内存大体分为:内核使用的内存、分配给普通进程使用的内存、空闲空间。要注意空间划分不保证是连续的。
内存管理主要研究的问题
- 管理哪些内存已经被分配,哪些内存暂时末被分配
- 已经分配出去的内存,何时进行回收、如何进行回收
- 物理地址和线性地址转换
- 内存碎片
进程通信IPC(Inter Process Communication)
理论上进程是独立的,但实际中往往是多个进程相互配合,来完成复杂的工作。因为OS进行资源分配是以进程为基本单位分配的,包括内存。分配给A进程的内存不会分配给B进程。所以,进程A、B之间直接通过内存来进行数据交换的可能性完全不存在了。所以OS需要提供一套机制, 用于让A、B进程之间进行必要的数据交换一进程间通信。
进程间通信的常见方式
- 管道(pipe)
- 消息队列(message queue)
- 信号量(semaphore)
- 信号(signal)
- 共享内存(shared memory)
- 网络(network)
线程(Thread)
概念
(1)什么是线程
线程是OS进行调度(分配CPU)的基本单位。一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着 多份代码.
(2)进程(process) 和线程(thread) 的关系
进程和线程是一对多的关系的关系。一个线程一定属于一个进程;一个进程下可以允许有多个线程。
一个进程内至少有一个线程, 通常被这个一开始就存在的线程,称为主线程(main thread)。主线程和其他线程之间地位是完全相等的,没有任何特殊性。
(3)为什么OS要引入thread这一个概念。
由于进程这一个概念天生就是资源隔离的,所以进程之间进行数据通信注定是一个高成本的工作。
现实中,一个任务需要多个执行流一起配合完成,是非常常见的。所以,需要一种方便数据通信的执行流概念出来,线程就承担了这一职责。
(4)线程和进程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
- 进程是包含线程的, 每个进程至少有一个线程存在,即主线程。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
- 进程和进程之间不共享内存空间.,同一个进程的线程之间共享同一个内存空间。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。
- 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
线程的创建
方法一:继承 Thread 类
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("good"+i);
}
}
public static void main(String[] args) {
//主线程
MyThread myThread = new MyThread();
//同时执行,主线程和上面的线程是同时执行的
myThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习"+i);
}
}
}运行结果:
可以看到主线程和上面的线程是同时执行的。
方法二: 实现 Runnable 接口
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象
创建Thread类对象,将 Runnable 对象作为参数,调用start()方法
public class MyThread_II implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("good"+i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
MyThread_II myThread = new MyThread_II();
//创建线程对象,通过线程对象开启线程(代理)
Thread thread = new Thread(myThread);
//Thread thread = new Thread(new MyThread_II());
thread.start();
}
}对比上面两种方法:
- 继承 Thread 类,直接使用 this 就表示当前线程对象的引用。
- 实现 Runnable 接口,this 表示的是 MyRunnable 的引用,需要使用 Thread.currentThread()
怎么去理解t.start()做了什么?
t.start()只做了一件事情:把线程的状态从新建变成了就绪。也就是说它不负责分配CPU。
线程把加入到线程调度器的就绪队列中,等待被调度器选中分配CPU。从子线程进入到就绪队列这一刻起,子线程和主线程在地位上就完全平等了。
所以,哪个线程被选中去分配CPU,就完全听天由命了。先执行子线程中的语句还是主线程中的语句理论上都是可能的。这就是为什么我们看到主线程和自定义的线程交替执行。
观察我们创建的第一个线程结果,大概率是主线程中的打印先执行,不是说两个线程地位平等吗?
t.start()是在主线程的语句。换言之,这条语句被执行了,说明主线程现在正在CPU上。(主线程运行状态),所以,主线程刚刚执行完t.start() 就马上发生线程调度的概率不大,因此大概率还是t.start()的下一条语句就先执行了。
区分 start() 和 run()
不要错误使用成 run() 方法,使用这个方法会被最先执行,而且在主线程使用,就是一个简单的方法调用。
使用 start():
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
//主线程
System.out.println("我是"+Thread.currentThread().getName());
}
}
//输出:
我是main
我是Thread-0使用run():
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.run();
//主线程
System.out.println("我是"+Thread.currentThread().getName());
}
}
//输出:
我是main
我是main我们写的无论是Thread的子类还是Runnable的实现类,只是给线程启动的“程序”。所以,同一个程序,可以启动多个线程。
示例:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是" + Thread.currentThread().getName());
System.out.println("我的id是" + Thread.currentThread().getId());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
MyThread t2 = new MyThread();
t2.start();
MyThread t3 = new MyThread();
t3.start();
MyThread t4= new MyThread();
t4.start();
}
}输出:
注意:每次输出的结果不是固定的哦
线程和方法调用栈的关系
每个线程都有自己独立的调用栈
public class Add {
public static int add(int a,int b){
return a + b;
}
}
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Add.add(5,5));
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
// 自己在主线程中也去调用 add 方法
System.out.println(Add.add(10, 20));
}
}
打上断点,右键选择 Thread。调试器中看到的方法调用栈每一行都是一个栈帧(frame),保存的就是运行方法时的临时数据,对于我们来说最主要的就是局部变量。

由于每个线程都是独立的执行流,A在调用过哪些方法,和B根本就没关系。表现为每个线程都有自己的独立的栈。上面是主线程的,下拉框可以选择别的线程


根据程序=指令+数据,此处我们调用的是同一个方法,说明执行的是同一批指令——把两个数相加并返回
因为栈不同(帧不同) :说明执行指令时,要处理的数据是不同。所以就可独立的对主线程和子线程,进行调试,二者互不影响。
理解栈和栈帧
在没有多线程和外部输入的情况下,程序的运行其实就是一个状态机

在函数调用过程中,会产生新的状态,最后返回的结果依然是最初的状态,只是数据发生了变化

如果发生了多次嵌套调用,像下面这样:
可以看到,这些框出现的顺序是 先进后出 因此,我们就可以使用栈去维护这些框。
栈:当前执行流的当前时刻(时间停止状态时)的状态框有哪些(现实方法的调用次序)
框:栈帧(frame)装的就是运行该方法时需要的一些临时数据(主要就是具备变量)
Thread的常见属性和方法
(1)常见属性
| 属性 | 获取方法 |
| ID | getId() |
| 名称(name) | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
- 本进程(JVM进程)内部分配的唯一的,只能 get不能set
- 默认情况下,如果没有给线程名字,线程名字遵守Thread-..;第一个是Thread-0、Thread-1、 Thread-2,主线程默认为 main。可以get也可以set,也可以是重复的
- 状态表示线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到。线程也可以set自己的优先级,但是要注意,优先级的设置,只是给JVM一些建议,不能强制让哪个线程先被调度。优先级最低为 1 ,最高为 10,默认为 5
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了 线程的中断问题
设置name:
public class SetNameThread extends Thread{
//方法一
// public SetNameThread(){
// setName("我是张三");
// }
//方法二
// public SetNameThread() {
// super("我是张三"); // 调用父类(Thread)的构造方法
// }
@Override
public void run() {
System.out.println(this.getName());
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
SetNameThread t1 = new SetNameThread();
//方法三
t1.setName("我是t1");
t1.start();
SetNameThread t2 = new SetNameThread();
t2.setName("我是t2");
t2.start();
}
}或者也可以创建有参构造来设置名字:
public class SetNameThread extends Thread{
public SetNameThread(String name){
super(name);
}
@Override
public void run() {
System.out.println(this.getName());
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
SetNameThread t1 = new SetNameThread("我是t1");
t1.start();
SetNameThread t2 = new SetNameThread("我是t2");
t2.start();
}
}上面的方法一、二是该类将线程的名字全都设置为一致的。
(2)等待一个线程-join()
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,A到饭店吃饭,发现没带钱包,于是让B回家拿钱包,那么A就必须等B把钱包拿来之后才能结账走人。
public class Main {
private static class B extends Thread {
@Override
public void run() {
// 模拟 B 要做很久的工作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
println("B 说:我的任务已经完成");
}
}
private static void println(String msg) {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date) + ": " + msg);
}
public static void main(String[] args) throws InterruptedException {
B b = new B();
b.start();
println("A 自己先去吃饭");
b.join();
println("A 说:B 给我把钱送来了,结账走人");
}
}
//输出:
2022-06-21 17:34:46: A 自己先去吃饭
2022-06-21 17:34:51: B 说:我的任务已经完成
2022-06-21 17:34:51: A 说:B 给我把钱送来了,结账走人可以发现,一定是等B拿来了钱包之后A还能结账走人。使用b.join(); 后, 目前的线程会阻塞,直到B运行结束才返回,这个时候B一定已经结束了。
同时join方法也有重载形式:
- join(long millis)等待线程死去,但最多等millis毫秒
- join(long millis, int nanos)时间单位更精确
(3)休眠当前线程
- TimeUnit.SECONDS.sleep(秒数);——以秒为单位
- Thread.sleep(秒数);——以毫秒为单位
让线程休眠,就是让当前线程从 运行 -> 阻塞,等待要求时间过去之后,线程从 阻塞 -> 就绪
(4)获取当前线程引用——Thread.currentThread()
public class Main {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
//输出:main(5)让线程让出CPU——yield( )
让线程从运行状态变为就绪状态
public class YieldTest extends Thread{
@Override
public void run() {
while (true){
System.out.println("我是"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
YieldTest t1 = new YieldTest();
t1.setName("张三");
YieldTest t2 = new YieldTest();
t2.setName("李四");
t1.start();
t2.start();
}
}观察运行结果我们能看到两个线程出现的概率是差不多的,如果这个时候,李四高风亮节,选择让出CPU,让我们看看结果如何
public void run() {
while (true){
System.out.println("我是"+Thread.currentThread().getName());
if(Thread.currentThread().getName().equals("李四")){
Thread.yield();
}
}
}运行之后我们发现,基本上打印的都是张三,但不是完全没有出现李四哦。
yield主要用于执行一些耗时较久 的计算任务时,为让防止计算机处于“卡顿”的现象,时不时的让出一些CPU资源,给OS内的其他进程。
(6)中断一个线程
方法一:deprecate 暴力停止,直接kill掉线程,目前基本上已经不采用了。原因是不知道该线程把工作进行的如何了
方法二:interrupt();A想让B停止,A给B主动发一个信号,B在一段时间里,看到了停止信号之后,就可以主动,把手头的工作做到一个阶段完成,主动退出。
那么B怎样感知到有人让它停止:
- 通过interrupted()方法,检查当前线程是否被终止,true:有人让我们停止false: 没人让我们停止
- B可能正处于休眠状态,JVM的处理方式是,以异常形式,通知B : InterruptedException
要注意,B具体要不要停,什么时候停,怎么停,完全自己做主
线程的状态
- NEW: 安排了工作, 还未开始行动
- RUNNABLE: 可工作的, 又可以分成正在工作中和即将开始工作。
- BLOCKED: 排队等着其他事情
- WAITING: 排队等着其他事情
- TIMED_WAITING: 排队等着其他事情
- TERMINATED: 工作完成了

线程状态只能获取,不能设置。
前台线程和后台线程
前台线程一般是做一 -些有交互工作的,后台线程又称精灵线程或者守护线程,一般是做一 些支持工作的线程。
比如:一个音乐播放器1.线程响应用户点击动作(前台)2.线程去网络上下载歌曲(后台)
要注意:当所有的前台线程都退出了,JVM进程就退出了,即使后台线程还在工作,也正常退出
JDK中自带的观察线程的工具——jconsole
该工具可以用来观察JVM运行中的一些相关情况,比如内存、类加载情况、线程情况

打开后找到idea启动的线程名称,双击进去,就可以观察了。

边栏推荐
- More than observation | Alibaba cloud observable suite officially released
- Simplest DIY remote control computer system based on STM32 ② (wireless remote control + key control)
- 坦然面对未来,努力提升自我
- ESP32-CAM无线监控智能网关的设计与实现
- 華為雲如何實現實時音視頻全球低時延網絡架構
- 一年多时间时移世易,中国芯片不断突破,美国芯片却难以卖出
- Analysis of LinkedList source code
- Deep dive kotlin synergy (XIV): problems of shared state
- 直播带货app源码搭建中,直播CDN的原理是什么?
- The simplest DIY serial port Bluetooth hardware implementation scheme
猜你喜欢

连番承压之后,苹果或将大幅提高iPhone14的售价

Tamidog | analysis of investor types and enterprise investment types

Maui uses Masa blazor component library

Attack and defense drill collection | 3 stages, 4 key points, interpretation of the blue team defense whole process outline

The simplest DIY pca9685 steering gear control program based on the integration of upper and lower computers of C # and 51 single chip microcomputer

ESP32-CAM、ESP8266、WIFI、蓝牙、单片机、热点创建嵌入式DNS服务器

【ML】QuantileRegressor

Win10 Microsoft input method (Microsoft Pinyin) does not display the word selection column (unable to select words) solution

Win10 微软输入法(微软拼音) 不显示 选字栏(无法选字) 解决方法

开发增效利器—2022年VsCode插件分享
随机推荐
网上注册股票开户很困难么?现在网上开户安全么?
Face the future calmly and strive to improve yourself
1路百兆光纤收发器1百兆光1百兆电桌面式以太网光纤收发器内置电源
十大劵商如何开户?在线开户安全么?
[use of Matlab graphical user interface controls]
汉源高科1路千兆光口转4路千兆以太网电口千兆1光4电光纤收发器
自动化或电气专业必备软件
Comment Huawei Cloud réalise l'architecture mondiale de réseau audio - vidéo en temps réel à faible latence
32路电话+2路千兆以太网32路PCM电话光端机支持FXO口FXS语音电话转光纤
过采样系列一:采样定理与过采样率
【黄金分割点】与【斐波那契数列】
汉源高科USB2.0光端机USB2.0光纤延长器USB2.0光纤传输器USB2.0接口转光纤
ESP32-C3入门教程 问题篇⑧——blufi_example.c:244: undefined reference to `esp_ble_gap_start_advertising
Maui uses Masa blazor component library
5 个关于 NFT 的技术漏洞
[cloud resident co creation] in the code free era, how does software development go to everyone?
消息队列的丢失、重复与积压问题
最简单DIY基于蓝牙、51单片机和舵机的钢铁爱国者机关枪控制器
1154. 一年中的第几天
Torch weight to mindspore