当前位置: 首页 > news >正文

多线程(1)

1进程

1.1概念

运行起来的程序叫进程,进程也是计算机分配资源的最小单位,进程是操作系统对正在运行的一程序的抽象描述,进程是抽象的是我们人为想象出来的一个东西,是一种逻辑,就是代码程序一直搁那跑,这个跑的过程就是进程,要描述这个抽象的东西那就得请'''类'

1.2如何描述进程?

站在操作系统(op)管理进程分两步

1描述进程

{利用一个类来描述每个进程,这个类叫做PCB(进程控制块),里面有很多属性,每个进程对应于一个PCB实例对象,用这个对象来指代每个进程}

2PCB的管理

{利用合适的数据结构对每个PCB对象进程整合管理,那么也就相当于在管理每个PCB对象所对应1的进程,进而实现对进程的管理,具体体现----->

1)创建一个新的进程

创建一个PCB对象把所有属性初始化然后把PCB加到集合当中

2)销毁一个进程

把这个进程的PCB从集合中删除掉

3查看进程列表

遍历集合结构,取出集合结构上的所有元素PCB对象,把里面的关键信息显示到界面上

}

1.3PCB类的属性

{1---PID(进程的身份标识符,一个编号,就类似身份证)

  2----内存指针(一组指针)----方便cup执行指令的时候知道去哪取指令,指令依赖的数据在哪

  3---文件描述表

      进程执行就是cpu执行相应的任务,那么就需要进行磁盘数据的读取,需要打开磁盘上的相关文件然后读取数据,那么读取到的数据就记录再这个文件描述表当中,此外一些网络资源读取之后也是以文件形式管理的,也会将存网络资源的相关文件内的数据读到文件描述表当中,方便后续使用数据.

4---进程的状态

1就绪状态---随叫随到,cpu叫你了,给你分配资源了你这个进程立马能跑起来,或者正处于运行状都叫就绪状态.

2阻塞状态---cpu叫你了,但你这个进程仍然阻塞,不适合在cpu上运行,因为cpu给你分配资源你也不跑,cpu就会回收这个资源分配给其他进程,这样的一个状态

5------进程优先级

有多个进程竞争一个cpu资源,有些进程就是优先级要高一些,cpu会优先选择给这些进程分配更多资源,让他们先跑,比如你再玩一款大型3a游戏,这时cpu的80%-90%的资源都分配给这款游戏了,后台挂的啥微信qq啥的就不会那么占资源.

6---进程的上下文

当一个进程执行中途突然结束,然后资源分配给其他进程,等到下次再次得到资源再次运行此进程不会重头开始跑,会有一个保存上下操作---->寄存器中会存一些当前进程执行的状况,比如执行到第几条指令了之类,当进程中断时会将这些信息存到内存当中,下一次又会恢复上下文----->将之前存的信息又提到到寄存器里面从上一次结束位置继续执行后续指令.类似游戏的存档-----读档

7---进程的记账信息

统计功能---如果操作系统发现某个进程吃的资源一直特别少,这时操作系统就会倾斜的多分配一些资源给当前进程

}

1.4并发执行和并行执行

并发执行:多个人排队上同一个厕所,只有当里面的人出来后,外面的其中一个人才能进厕所,而且是随机概率----->对应到操作系统调度的随机性,多个进程的调度是随机的,并且出来的人任然还有可能进厕所.

并行执行:有三个厕所,三个人,那么机不需要等待了,多个人同时上厕所,一并上厕所,就是并行执行

2线程

2.1线程概念

⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏ 着多份代码.

2.2为什么要有线程?

前面讲到的进程已经够好了啊,能够并发执行,符合我们这样一个cpu多核心的时代,让多个线程在多个核心上跑起来,但是我们注意到一个问题,我们这样一个并发编程就意味着需要更多的进程,但是进程是资源分配的单元啊,没弄一个新的进程出来也就意味着更多资源需要被占用开销,不够轻量,然而我们的线程就足够轻量

体现在三个方面

•创建线程⽐创建进程更快.

• 销毁线程⽐销毁进程更快.

• 调度线程⽐调度进程更快.

解释:

创建进程,需要申请资源,这个申请过程是需要时间的,但是在进程当中创建线程,创建这个线程所需的资源就是创建进程时申请的那份资源,这份资源可以被重复利用,重复的用于创建线程

2.3进程和线程的区别:(重点)

1:进程包含线程,每一个进程至少包含一个线程,即主线程

2:进程和进程之间不共享内存空间,进程中的线程之间共享内存空间

3:进程是系统分配资源的最小单元,线程是系统调度的最小单元,即线程是cpu上执行任务的最小单元.        

4:一个进程挂了一般不会影响到其他进程,但是一个线程挂了,可能把同进程内的其他线程一起带走

2.4创建多线程的方法

1继承Thread重写run

2实现Runnable接口重写run

3匿名内部类创建Thread类的子类

4匿名内部类创建Runnable接口的实现类对象

5lambda表达式创建Runnable接口实现类对象

2.5前台线程和后台线程

前台线程:决定进程终止与否

后台线程:与进程终止与否无关,前台线程全部关闭,进程关闭,后台线程随之关闭,后台线程关闭,进程不关闭,前台线程也不关闭------后台线程是jvm自带的线程,它们的存在不影响线程的存在,他们是一些具有特殊功能的线程,比如说垃圾回收,随着进程的执行而执行.

自己默认创建的线程都是前台线程,可以通过在开启线程之前setDaemon来设置前台线程为后台线程

public static void main(String[] args) {Thread t=new Thread(()->{System.out.println("设置后台线程");});//t成--后台线程与进程终止无关`t.setDaemon(true);t.start();}

3Thread类和常见方法

3.1Thread类常见的构造方法和属性

属性:

3.2线程的中断

线程的中止要么就是快点让线程走完了,return了那么就线程就中止了完事了,但是还有什么方法能够让线程提前中止呢?有如下方法---->

方法1通过共享的标记来进⾏沟通

//线程中断方法1=---使用自定义的变量来作为标志位private static boolean isQuit=false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!isQuit) {try {System.out.println("转账中");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("转账终止");});System.out.println("开始转账");t.start();Thread.sleep(1000*10);System.out.println("突然有人说停止转账");isQuit=true;}

那么其实在jvm中也提供了上述的一个标记,并且提供相关的方法对其进行修改和查询

修改的方法为 interrupt()--->能够将标记修改成中断线程的状态,一旦标记处于线程中断的这样一个状态,那么就意味着线程得马上进行中断了.

查询方法:

1:Thread.interrupted()

2:Thread.currentThread().isInterrupted()

两者的区别在于----第一种方法在查询后会将标记摸除,也就是说会将标记从中断状态恢复为原来的的进行状态,而第二种不会

方法2:调⽤interrupt()⽅法来通知

public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.interrupted()){System.out.println("转账中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println("提前终止转账");break;}}System.out.println("转账提前终止成功");});System.out.println("开始转账");//开始转账线程t.start();Thread.sleep(10*1000);System.out.println("请提前转账终止");t.interrupt();}
public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("adceg");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println("提前中断线程");break;}}System.out.println("线程中断成功");});t.start();Thread.sleep(5*1000);//interrupt()方法是手动将线程设置为中断状态t.interrupt();}

特别注意的细节break不能少:

sleep被唤醒后,它会将标记恢复原状态,和Thread.interrupted()方法类似,如果将break去掉,那么循环将继续无法达到提前终止线程的效果

3.3线程的休眠

方法:

让目前线程把cpu资源释放出来给其他线程,自身处于休眠状态,但是实际的休眠时间大于等于设置的休眠时间,因为线程是随机调度的,比如我休眠1000ms,休眠完事之后,就会请求cpu给我们的线程分配资源,也就是线程的调度,调对应的线程到cpu上去跑,有那么多线程,你无法保证第一时间就调度到你,也,就需要多出来一部分时间来等待被调度到,此时线程仍然处于休眠状态,这时休眠时间就超设置值了,如果比较幸运休眠完事之后立马被调度,那么就实际休眠时间就等于设置时间.

public static void main(String[] args) throws InterruptedException {long a=System.currentTimeMillis();Thread.sleep(3 * 1000);long b=System.currentTimeMillis();System.out.println(b-a);}

执行结果:很明显多了3ms

3.4等待一个线程

方法:

解释一下join:

那个线程调用join()方法那么那个线程就是被等的线程,那么开启这个被等待的线程的外层线程就是等待的线程,也就是说,外层线程必须等待在你里面开启的这个子线程执行完事之后才会继续往下走.

比如下述例子,main线程必须等t线程执行完毕才能往下走,在那个线程中调用join()方法那个线程就是等待的线程,这个例子就是main线程在等待,那个线程调用方法那个线程就是被等待的线程,这个例子就是t线程

 public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{System.out.println("子线程");});t.start();t.join();//相当于将main线程给中断了,搁着阻塞等待System.out.println("t线程结束,跳过join,继续执行mian线程后续的相关操作");}

join()不带参数的构造方法不够科学,因为如果被等线程一直没结束,那么就需要一直死等下去,这样并不好,所以最好用带时间的,超出时间限制就不等了.

为什么不用sleep来等待呢?

首先sleep的话不知道要休眠多久,少了,就可能导致t线程还走完,main线程就提前结束了,多了,那么就导致无法在t线程结束的第一时间去继续执行main线程,还需要在休眠一段时间.

线程等待的作用?

我们知道线程调度是随机的,两个线程即使都start了,一个前一个后,这个线程执行的前后顺序是未知的,既然无法决定你谁先开始,那么我们可以通过join()线程等待来决定线程结束,比如

代码块1:

public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{System.out.println("第一个线程");});Thread t2=new Thread(()->{System.out.println("第二个线程");});t1.start();t2.start();System.out.println("mian线程结束");}

 代码块2:

public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{System.out.println("第一个线程");});Thread t2=new Thread(()->{System.out.println("第二个线程");});t1.start();t1.join();//必须等待t1线程执行完事之后才能开启t2线程t2.start();t2.join();//必须t2线程跑完才能main结束System.out.println("mian线程结束");}

比较代码块1和2,代码块1是无法控制t1 t2谁先谁后执行的,因为线程的调度是随机的,线程开始了并不意味着就执行了,要操作系统给你分配cpu资源才能正式执行.就好像有了车票就一定能马上上车吗?得先一个一个排队检票,到你检票了,检完票才能上车

代码块2中,线程t1开始了,执行完了,然后才轮到t2线程开始执行,所以可以确定的是t1线程一定在t2线程前先执行,这样我们就从代码的层面控制了线程的执行先后顺序,虽然对于操作系统而言线程调度是随机的,线程执行是无序的,但是我们还是可以人为控制一些先后顺序的,这在实际开发中是有必要的.

4启动一个线程start

new线程当中重写run方法不意味着线程被启动了,run只是线程的入口,你这个线程具体要干点啥,执行啥任务,都在重写的run方法的方法体 中体现.   start是将这个线程启动,处于就绪状态,当有cpu资源分配了,线程才真正开始执行,就像冬天启动摩托车一样,先哄几下油门,热下车,但是车没走,这个就相当于线程的start,当挂上档车开始走了,就相当于线程被分配资源了开始真正执行任务了.

http://www.xdnf.cn/news/679681.html

相关文章:

  • async和await如何捕获异常
  • 两个线程交替打印1-100
  • UWB:litepoint获取txquality里面的NRMSE
  • CSV数据处理全指南:从基础到实战
  • 第六十八篇 从“超市收银系统崩溃”看JVM性能监控与故障定位实战
  • 递归函数,数学表达式转化成递归函数
  • Spring Boot 深度集成 Ollama 指南:从聊天模型配置到生产级应用开发
  • 【STM32】HAL库 之 CAN 开发指南
  • 常用的数据分布
  • [小白]Docker部署kingbase(人大金仓)数据库[超详细]
  • win11如何重启
  • 算法打卡第八天
  • 工业控制系统的神经网络:TSN交换机是如何改变自动化通信的?
  • Python训练营打卡Day38
  • 【DSP笔记】解锁频率之秘:Z 变换与离散傅里叶变换的深度探索
  • 一些视觉应用中的数学小知识点总结
  • Mate桌面环境系统与终端模拟器参数配置
  • ai客服平台哪家好:AnKo多模型AI聚合时代!
  • Python实现自动物体识别---基于深度学习的AI应用实战
  • 【Git】Commit Hash vs Change-Id
  • 浏览器缓存详细介绍
  • API平台(API网关)的API监控预警机制
  • 欧几里得 ---> 裴蜀定理 ---> 拓展欧几里得
  • 使用MATLAB求解微分方程:从基础到实践
  • ProfiNet转MODBUSTCP网关模块的实时性保障Logix5000控制器与AltivarProcess变频器同步控制方案
  • 【leetcode】977. 有序数组的平方
  • Microbiome|基于MAG的宏转录组
  • TailwindCSS v4 快速入门教程
  • 在Linuxfb环境下利用海思TDE API实现高效的2D图形加速
  • Java中的日期类详解