• 多线程笔记第一天(进程的理解、线程的理解与创建、Thread类、线程状态)


    目录

    1.进程的理解 

    1.1 进程管理

    1.2 PCB属性

    1.3 虚拟地址空间&进程间通信

    ​编辑

    2.多线程的理解 (Thread)

    2.1 线程概念

    2.2  线程的作用

    2.3 进程与线程的区别(重点)

    2.4 多进程需要注意的问题

    3.线程的创建

    3.1 创建线程(继承Thread类)

     3.2 Jconsole工具查看线程

     3.3 创建线程其他方法

    3.3.1 实现Runnable接口,重写run方法

    3.3.2使用匿名内部类,实现创建Thread子类的方式

     3.3.3使用匿名内部类,实现Runnable接口的方法

    3.3.4 lambda表达式

    4.Thread类的常见方法

    4.1 Thread常见的构造方法

     4.2  Thread的常见属性

     4.3 线程启动(start和run的区别 重点)

    4.4 线程中断(isInterruptted)

     4.5 线程等待(join)

    4.6 获取当前线程引用

    5.线程状态


    1.进程的理解 

    1. //进程是操作系统对一个正在运行的程序的一种抽象,通俗的说,进程就是运行起来的程序;
    2. //同一时刻系统中的进程会有很多,而操作系统就得给他们安排好
    3. //就是说: 进程实时操作系统分配资源的基本单位

    1.1 进程管理

    1. //进程管理 = 描述 + 组织
    2. //描述:详细的表示一个进程中有哪些属性/信息,而结构体就是用来描述的;
    3. //一个结构体对象对应一个进程 结构体也叫PCB(进程控制块)
    4. //组织:通过一定的数据结构,将若干个用来描述的实体组织到一起,进行增删改查;
    5. //系统通常会使用 双向链表 这样的结构来将 PCB 组织起来
    6. //

     创建一个进程,本质上就是创建PCB,给进程分配资源赋值到PCB中,并且加入到链表上;

    销毁一个进程,本质上就是从链表上删除对应的PCB节点,是否PCB持有资源

    查看任务管理器的进程列表,本质上就是遍历这个链表

    一个进程也可以是多个PCB ,系统管理PCB的链表也可以是多个

    1.2 PCB属性

    1.  PID 进程的身份表示

    一个主机,同一时刻,进程的PID是唯一的(相当于身份证号码),可以通过PID区分进程

    2.   内存指针:描述了这个进程使用的内存空间是那个范围(分配空间范围)

    3.    文件描述符:描述了这个进程打开哪些文件(文件就是存储在硬盘上的数据)

    进程调度:

    并行执行:每个CPU核心上,都可以独立运行一个进程

                       多个CPU核心上,就可以独立运行多个进程

    并发执行:一个CPU核心上,先运行进程1,再运行进程2,再运行进程3....

                      宏观上,同时运行多个程序;微观上,同一时刻运行一个程序

    4. 进程状态: R(可执行状态)   S(可中断睡眠状态)  X(退出状态)   D (不可中断的睡眠状态)

    T(暂停状态)   Z(僵尸状态) [Linux进程状态解析之 R、S、D、T、Z、X (主要有三个状态)]

    阻塞状态的进程: 无法被调度到CPU上执行

    就绪状态的进程: 才可以在CPU上执行

    5.  进程优先级:优先给谁安排谁先执行

    系统调度的时候,就会根据优先级,来给进程安排执行时间

    创建进程时,也可以通过系统调用来干预优先级

    6. 进程上下文: 主要存储调度出CPU之前,寄存器的信息(把寄存器的信息保存到内存中),等到这个进程下次恢复到CPU上执行时,将内存数据恢复到寄存器中

    通俗的说 就是 “读档” 与 “存档”

    进程在CPU上执行,要切换到别的进程,就需要保存当前运行结果(存档),下次再到他执行时,就恢复之前的中间结果(读档),然后继续执行

    7. 进程的记账信息:记录进程在CPU上的执行时长

    通过记账信息来决定,进程在CPU上继续执行,还是调度出去

    1.3 虚拟地址空间&进程间通信

    为了不出现进程间的相互影响,指针越界操作等情况,就需要让每一个进程都有各自的内存空间,不让这些进程的活动范围重叠,给每个进程都划分内存空间,叫做“虚拟地址空间”(不是真正的物理内存地址),通过专门的设备MMU来完成虚拟地址,到物理之前的映射

     使用虚拟地址空间,就让进程存在了“隔离性”

    (一个进程不能直接访问另一个进程的内存数据,防止干扰操作,提高系统的稳定性 )

    但这样就会导致进程之间很难进行交互,为了解决这个问题就有了“进程间通信

    进程间通信 : 核心原理就是,找多个进程都可以访问到的公共资源,然后基于公共资源进行数据交换。

    主流操作系统提供的进程通信机制有:文件、Socket(网卡)、管道、消息队列、磁盘、信号量、信号

    2.多线程的理解 (Thread)

    2.1 线程概念

    一个线程就是一个执行流(按照一定顺序执行的一组代码),

    每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行多份代码

    同一个进程中的这些线程,共用一份系统资源(内存+文件)

    进程是 资源分配的 基本单位

    线程是 调度执行的 基本单位

    2.2  线程的作用

    首先是,因为“并发编程“可以充分利用CPU

    其次,多进程虽然也可以实现并发编程,但线程比进程更轻量

    (创建、销毁、调度线程都比进程更快)

    所以,使用多线程:

    1.能够充分利用多核CPU,能够提高效率  

    2.只是创建第一个线程时,需要申请资源,后续再创建新的进程,都是公用一份资源

    销毁线程时,也是销毁到最后一个的时候,才真正释放资源,前面的线程销毁,都不必真正的释放资源

    最后又有了更好的办法 "线程池"和"协程"

    2.3 进程与线程的区别(重点

    1. 进程包含线程(多个)

    2. 线程比进程更轻量(创建、销毁、调度更快)

    3. 同一个进程的多个线程之间共用一份内存/文件资源

        进程和进程之间,则是独立的内存/文件资源

    4. 进程是操作系统资源分配的基本单位

        线程是操作系统调度执行的基本单位

    2.4 多进程需要注意的问题

    1. 多线程 一定程度下,线程数目越多,效率越高;但CPU核心数是有限的,当线程数目多到一定程度的时候,CPU核心就被占满了,线程调度开销太大,反而效率变慢

    2.  当两个线程去争夺同一资源时,此时两个线程就出现了问题,这就会使:”线程不安全

    3.   如果某个线程出现异常,并且异常没有处理好,此时就可能出现整个进程崩溃,此时其他线程也会运行出现问题

    3.线程的创建

    3.1 创建线程(继承Thread类)

    继承Thread类,重写run方法

    1. class MyThread extends Thread {
    2. @Override
    3. public void run() {
    4. System.out.println("Thread");
    5. }
    6. }

     标准库中,提供一个Thread类,使用的时候就可以继承这个类

    Thread类,相当于是对操作系统中的线程进行的封装

    重写run方法,run是Thread父类中已有的方法,但是这里要重写

    run中的逻辑就是这个线程要执行的工作

    创建子类,重写run方法,相当于”安排任务“

    1. public class Demo {
    2. public static void main(String[] args) {
    3. MyThread myThread = new MyThread();
    4. myThread.start();
    5. }
    6. }

     创建MyThread实例,并不会在系统中真的创建一个线程

    而是,调用start方法的时候,才会真正的创建线程

    新的线程才会执行run方法中的逻辑,直到run中的逻辑执行完,新的线程才会运行结束

    1. class MyThread01 extends Thread {
    2. @Override
    3. public void run() {
    4. while (true) {
    5. System.out.println("Hello Thread!");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. }
    13. }
    14. public class Test {
    15. public static void main(String[] args) throws InterruptedException {
    16. MyThread01 myThread = new MyThread01();
    17. myThread.start();
    18. while (true){
    19. System.out.println("hello main");
    20. Thread.sleep(1000);
    21. }
    22. }
    23. }

    抢占式执行 

     3.2 Jconsole工具查看线程

    第一步:找到Jdk安装路径在bin下找到——>jconsole.exe打开

     第二步:启动测试类,在本地进程中选择当前测试项目,连接后就可查看项目中的线程

    第三步:选择查看线程信息 

     3.3 创建线程其他方法

    3.3.1 实现Runnable接口,重写run方法

    创建Runnable实例,将Runnable实例作为参数传给线程

    将线程要干的工作 和线程本身 分开了,使用Runnable来专门表示”线程要完成的工作“

    这种方法的好处是, 降低了耦合度,如果以后要修改代码,改动的相对较少

    只需要将修改后的Runnable传给其他的实体就可以了

    并且如果是多个线程干一样的工作,Runnbale也更适合

    1. //创建一个Runnable的实现类,并实现Run方法
    2. //Runnable主要描述的是线程的任务
    3. class MyRunnable01 implements Runnable {
    4. @Override
    5. public void run() {
    6. System.out.println("hello thread");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. }
    14. /**
    15. * 通过继承Runnable接口并实现run方法
    16. */
    17. public class Thread_demo02 {
    18. public static void main(String[] args) throws InterruptedException {
    19. //实例化Runnable对象
    20. MyRunnable01 runnable01 = new MyRunnable01();
    21. //实例化线程对象并绑定线程
    22. Thread thread = new Thread(runnable01);
    23. //真正的去申请系统线程参数CPU调度
    24. thread.start();
    25. while (true) {
    26. System.out.println("hello main");
    27. Thread.sleep(1000);
    28. }
    29. }
    30. }

    3.3.2使用匿名内部类,实现创建Thread子类的方式

    创建Thread的子类,同时实例化出一个对象

    1. public static void main(String[] args) {
    2. Thread thread = new Thread() {
    3. @Override
    4. public void run() {
    5. while (true) {
    6. System.out.println("hello Thread");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. }
    14. };
    15. thread.start();
    16. }

     3.3.3使用匿名内部类,实现Runnable接口的方法

    匿名内部类的实例作为构造方法的参数

    1. public static void main(String[] args) {
    2. Thread t = new Thread(new Runnable() {
    3. @Override
    4. public void run() {
    5. System.out.println("hello Thread");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. t.start();
    14. }

    3.3.4 lambda表达式

    1. public static void main(String[] args) {
    2. Thread thread = new Thread(()->{
    3. while (true){
    4. System.out.println("hello Thread");
    5. try {
    6. Thread.sleep(1000);
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. }
    11. });
    12. thread.start();
    13. }

    4.Thread类的常见方法

    4.1 Thread常见的构造方法

    方法说明
    Thread()创建线程对象        
    Thread(Runnable target)使用Runnable对象创建线程对象
    Thread(String name)创建线程对象,并命名
    Thread(Runnable target,String name)使用Runnable对象创建线程对象,并命名
    使用构造方法给线程命名

    1. //使用构造方法给线程命名
    2. public class Demo2 {
    3. public static void main(String[] args) {
    4. Thread thread = new Thread(()->{
    5. while (true) {
    6. System.out.println("hello aka yty");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. },"线程名字:小杨要坚强");
    14. thread.start();
    15. }
    16. }

    使用Jconsole.exe 查看线程名称:

     4.2  Thread的常见属性

    属性获取方法
    IDgetId()
    名称getName()
    状态getState()
    优先级getPriority()
    是否后台线程isDaemon()
    是否存活isAlive()
    是否被中断isInterrupted()

    (1)getId() 这个是Java中给Thread对象安排的身份标识, 和操作系统内核中PCB的pid、以及操作系统提供的线程API的线程id都不是一个

    (2)isDaemon() 判断是否是后台(守护)线程。默认创建的线程是”前台线程“,前台线程会阻止进程退出,如果main运行完,前台线程还没完,进程不会退出;如果是”后台线程“,后台线程不会阻止进程退出,如果main执行完其他的前台进程执行完,此时即使后台线程没执行完,也会退出。

    ”只要有一个前台(main也是一个前台线程)线程,进程就还活着,前台没了,后台也就没了“。

    设置操作得在sttart之前,如果线程启动,就不能修改

    1. public class isDaemon {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(()->{
    4. while (true){
    5. System.out.println("小杨");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. //使用setDaemon设置为后台线程
    14. //设置操作在start之前
    15. thread.setDaemon(true);
    16. thread.start();
    17. System.out.println("main线程执行结束!");
    18. }
    19. }

    (3)isAlive()判断内核中线程是否存活。Thread对象,虽然和内核中的线程,具有一一对应关系,但是生命周期并非完全相同;Thread对象存在了,内核里的线程不一定有,调用start方法,内核线程执行完(run运行完),内核的线程就销毁了,但是Thread对象还在。

    (4) Thread属性

    1. public class ThreadDemo {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(()->{
    4. while (true){
    5. System.out.println("小杨");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. thread.start();
    14. System.out.println(thread.getId());//ID
    15. System.out.println(thread.getName());//名称
    16. System.out.println(thread.getPriority());//优先级
    17. System.out.println(thread.getState());//状态
    18. System.out.println(thread.isDaemon());//是否为后台线程
    19. System.out.println(thread.isAlive());//是否存活
    20. System.out.println(thread.isInterrupted());//是否被中断
    21. }
    22. }

     

     4.3 线程启动(start和run的区别 重点

     调用start才会真正创建线程,不调用start就没有创建线程(在内核里创建PCB)

    start和run的区别(*)

    直接调用run,并没有创建线程,只是在原来的线程中运行代码

    调用start,则是创建了线程,在先线程中执行代码

    作用和功能不同:

    1. run方法的作用是描述线程具体执行的任务

    2. start方法的作用是真正的去申请系统线程

    运行结果不同:

    1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;

    2. start调用方法后,start方法内部会调用Java本地方法(封装了对系统底层的调用)真正的启动线程,并且执行run方法中的代码,run方法执行完成后线程进入销毁阶段。

    4.4 线程中断(isInterruptted)

    线程中断,本质就是让run方法尽快结束,而不是run执行一般,强制结束。

    方法一:

    直接自己定义标志位,作为线程结束的标志

    1. public class isInterrupted {
    2. private static boolean isQuit = false;
    3. public static void main(String[] args) {
    4. Thread thread = new Thread(() -> {
    5. while (!isQuit) {
    6. System.out.println("小杨");
    7. try {
    8. Thread.sleep(1000);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. System.out.println("thread线程执行完了");
    14. });
    15. thread.start();
    16. try {
    17. Thread.sleep(3000);
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. }
    21. isQuit = true;
    22. System.out.println("设置标志让thread结束");
    23. }
    24. }

    方法二:

    使用标准库中自带的标记位

    1. public class isInterrupted {
    2. public static void main(String[] args) {
    3. Thread thread = new Thread(() -> {
    4. while (!Thread.currentThread().isInterrupted()){
    5. System.out.println("小杨");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. System.out.println("thread线程执行完了");
    13. });
    14. thread.start();
    15. try {
    16. Thread.sleep(3000);
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. thread.interrupt();
    21. System.out.println("设置让thread结束");
    22. }
    23. }

     运行程序,出现异常,是因为interrupt方法的行为有两种情况

    1. thread线程在运行状态会设置,标志位为true

    2.  thread线程在阻塞状态sleep  不会设置标志位,而触发一个InterruptedException

    这个异常会把sleep提前唤醒。

    所以 想要顺利结束循环  由线程代码自身进行判定处理

    线程自身可以:

    (1) 立即结束线程(break)

    (2)啥也不干

    (3)稍后处理(sleep,break)

     4.5 线程等待(join)

    线程之间的调度顺序是不确定的,可以通过join来控制线程之间的结束顺序

    方法说明
    join()等待线程结束
    join(long millis)等待线程结束,最多等millions毫秒
    join(long millis,int nanos)也可以等待给更高精度 毫秒+纳秒

    在main中调用join效果就是:让main线程阻塞等待,等到thread执行完,main才继续执行

    1. public class join {
    2. public static void main(String[] args) throws InterruptedException {
    3. Thread thread = new Thread(() ->{
    4. for (int i = 0; i < 5; i++) {
    5. System.out.println("hello thread");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. thread.start();
    14. System.out.println("main线程join之前");
    15. thread.join();
    16. System.out.println("main线程join之后");
    17. }
    18. }

    4.6 获取当前线程引用

    currentThread(); 获取当前这个线程对于的Thread对象的引用 


    5.线程状态

    NEW: 安排了工作 , 还未开始行动(Thread对象创建出来了,但是内核的PCB还没创建,(还没真正创建线程))
    RUNNABLE: 可工作的 . 又可以分成正在工作中和即将开始工作(就绪状态(正在CPU上运行+在就绪队列中排队))
    BLOCKED: 这几个都表示排队等着其他事情(等待锁的时候进入阻塞状态)
    WAITING: 这几个都表示排队等着其他事情(特殊的阻塞状态,调用wait)
    TIMED_WAITING: 这几个都表示排队等着其他事情(按照一定的时间,进行阻塞sleep)
    TERMINATED: 工作完成了 (内核的PCB销毁了,但是Thread对象还在)
      

    1. public class getState {
    2. public static void main(String[] args) throws InterruptedException {
    3. Thread thread = new Thread(() -> {
    4. try {
    5. Thread.sleep(2000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. });
    10. //thread开始之前 得到的是 NEW
    11. System.out.println(thread.getState());
    12. thread.start();
    13. Thread.sleep(50);
    14. //thread正在工作,得到的是 RUNNABLE
    15. System.out.println(thread.getState());
    16. thread.join();
    17. //thread结束之后,得到的状态是 TERMINATED
    18. System.out.println(thread.getState());
    19. }
    20. }

     


  • 相关阅读:
    Spring 容器加载Bean过程分析
    双十二哪些数码好物值得入手?盘点双十二最值得入手的数码好物
    Spring MVC:数据绑定
    kafka 消息偏移量
    JDBC | JDBC API详解及数据库连接池
    Cisco简单配置(八)—动态中继协议DTP
    LabVIEW合并VI
    java专项练习(验证码)
    R语言计算两个向量成对元素的最小值:使用pmin函数计算dataframe数据中多个数据列的逐行最小值
    #边学边记 必修5 高项:对人管理 第2章 项目沟通管理和干系人管理 2-5 项目干系人管理
  • 原文地址:https://blog.csdn.net/weixin_53939785/article/details/127082562