多线程的学习第二篇

2023-09-21 01:20:22

previewfile_28144258300213

多线程

线程是为了解决并发编程引入的机制.
线程相比于进程来说,更轻量
~~ 更轻量的体现:

  • 创建线程比创建进程,开销更小
  • 销毁线程比销毁进程,开销更小
  • 调度线程比调度进程,开销更小

进程是包含线程的.
同一个进程里的若干线程之间,共享着内存资源和文件描述符表

每个线程被独立调度执行.每个线程都有自己的状态/优先级/上下文/记账信息

进程是操作系统资源分配的基本单位
线程是操作系统调度执行的基本单位


Thread类 ~~ 五种创建线程的写法

1.继承Thread,重写run
2.实现Runnable,重写run
3.使用匿名内部类,继承Thread
4.使用匿名内部类,实现 Runnable
5.使用lambda表达式

使用了不同的方式来描述, Thread里的任务是什么.
上述办法,只是语法规则不同,本质上都是一样的方式.
~~ 这些方法创建出来的线程,都是一样的.

对Thread的run方法和start方法的区别
run ~~ 描述了线程要做的工作.
start ~~ 真的在操作系统内核里弄了个线程,并且让新线程调用run方法.


Thread的用法

Thread 的常见构造方法

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

Thread(Runnable target, String name)
Thread(String name)
这2个方法的参数String name
=> 给线程起个名字 ~~ 为了方便调试
线程默认的名字,叫做thread-0之类的…
thread-1, 2, 3
为了加深印象,写个线程名字为myThread的代码,通过 JConsole 观察线程的名字

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("hello");
                }
            }
        },"myThread");
        t.start();
    }
}

image-20230920004942382

这图中没有主线程的存在 ~~ 主线程执行完了start之后,紧接着就结束了main方法.
对于主线程来说, main方法完了,自己也就没了.
myThread线程也是如此,run方法执行完了,此时线程也就没了.
图示:
image-20230920010312996

两个线程在微观上,可能是并行(两个核心),也可能是并发的(一个核心)
宏观上感知不到(应用程序这里),咱们看到的始终是随机执行,抢占式调度…
操作系统决定到底怎么使用CPU, 我们是不知道的.

Thread的几个常见属性

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

getId(): 获取到线程的ID, ID 是线程的唯一标识,不同线程不会重复
getName(): 获取到构造方法里的名字
getState(): 获取到线程状态, 状态表示线程当前所处的一个情况(Java里线程的状态要比操作系统原生的状态更丰富一些)
getPriority(): 优先级,这个不仅可以获取,还可以进行设置,优先级高的线程理论上来说更容易被调度到 ,但是呢,实际上起不到什么作用!!!
isDaemon(): 是否是“守护线程”,也可以叫做是“后台线程” ~~ 相对应的还存在“前台线程”这个概念.
~~ 玩手机的时候,手机上一般都会运行多个APP,在运行多个APP的时候,你打开微信,来聊天,此时微信就处在‘’前台‘’的状态.然后,你想打游戏,就切到王者荣耀的游戏界面.这时,微信就跑到了‘’后台‘’.
前台线程: 会阻止进程结束, 前台线程的工作没做完, 进程是不会结束的
后台线程: 不会阻止进程结束, 后台线程工作没做完, 进程也是可以结束的
代码里手动创建的线程,默认都是前台线程,包括 main 默认也是前台的.
其他的 JVM 自带的线程都是后台的
~~ 也可以使用 setDaemon 设置成后台线程.
是“后台线程”,就是‘’守护线程‘’.

注:要把线程是否是后台,和线程调度切换,区分开,两者不是一回事.
线程的后台前台,只是取决于你调用的setDaemon这个方法做了什么…
image-20230920132218590

isAlive(): 在真正调用start之前,调用t.isAlive 就是 false.调用start之后, isAlive就是 true.
在真正调用start之前,调用t.isAlive 就是 false.调用start之后, isAlive就是 true.
~~ 另外,如果内核里线程把 run千完了,此时线程销毁, pcb随之释放,
但是 Thread t这个对象还不一定被释放的,此时isAlive 也是 false.

image-20230920184520089

Thread t这个对象还不一定被释放的,此时isAlive 也是 false的例子:
image-20230920191341931

如果t的run还没跑, isAlive 就是false
如果t的run正在跑, isAlive 就是true
如果t的run跑完了, isAlive 就是false

时刻牢记!!线程之间是并发执行的,并且是抢占式调度的.

image-20230920195527226

线程调度,就好比,顾客们去餐馆吃饭一样.
如果把CPU 想象成餐馆,每个线程就是来餐馆吃饭的人
假设ABCD这四个人是天天来的常客 工
但你无法确定,今天来的这四个人的顺序是ABCD,还是DCBA,还是BADC……
由于每个人的行为之间都是独立的.A这个来的时间和BCD是没关系的.
即使假设,按照ABCD顺序来了.如果这四个人来的时间差很多,是一个接一个来的,此时也无法保证,上菜的顺序也是ABCD……. => 充满了完全不可预期的因素

以前写代码,只要读懂代码的顺序,就是固定按照从上到下的顺序来执行的…
但是现在不是了,理解多线程代码,要考虑无数种顺序.

中断一个线程

中断的意思是,不是让线程立即就停止,而是通知线程,你应该要停止了.
是否真的停止,取决于线程这里具体的代码写法.
例子: 暑假在家的时候,你正在打王者,突然,你妈喊你下楼去买包盐,
你的选择如下:
1.放下游戏,立即就去.
2.打完这把,再去,稍后处理.
3.假装没听见,就完全不处理.
不同的选择,就对应不同的结果

停止线程的方式

目前常见的有以下两种方式:

1.使用标志位来控制线程是否要停止.

示例代码

public class ThreadDemo8 {
    public static boolean flag = true;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (flag){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //在主线程里就可以随时通过 flag变量的取值,来操作t线程是否结束.
        flag = false;

    }
}

运行结果

image-20230920214459036

这个代码之所以能够起到,修改flag, t 线程就结束
=> 完全取决于t 线程内部的代码.代码里通过flag控制循环.
这个线程是否要结束,啥时候结束都是线程内部自己代码来决定的.
缺陷
自定义变量这种方式不能及时响应.尤其是在sleep 休眠的时间比较久的时候

2.使用Thread自带的标志位进行判定

~~ 它是可以唤醒上面的这个sleep这样的方法的.
示例代码

public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t =new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }
}

!Thread.currentThread().isInterrupted()
它是Thread类的静态方法,通过这个方法可以获取到当前线程.
哪个线程调用的这个方法,就是得到哪个线程的对象引用. ~~ 很类似于this
在博主所写的上述代码中,是在t.run中被调用的.此处获取的线程就是t线程.

isInterrupted() ~~ 前面加了个‘!,表示逻辑取反.
为true表示被终止.
为false表示未被终止.(应该要继续走)

t.interrupt(); => 终止 t 线程

​ 注: isInterrupted() => 这个方法背后,就相当于是判定一个boolean变量
t.interrupt() => 就是在设置这个 boolean 变量

​ main 线程调用 t.interrupt() 相当于main通知 t 线程你要终止了.

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

image-20230920234340825

image-20230921000821986

为啥 sleep要清除标志位?
唤醒之后,线程到底要终止,还是不要到底是立即终止还是稍后,就把选择权
交给程序猿自己了!!!

等待一个线程

~~ join()
线程是一个随机调度的过程.
等待线程,做的事情,就是在控制两个线程的结束顺序.
示例代码

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("join 之前");
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join 之后");
    }
}

运行结果

image-20230921004505244

画图对代码进行解释:

image-20230921004425415

主线程,等待 t 线程彻底执行完毕之后,才继续往下执行了

假设开始执行join的时候, t 线程已经结束了,此时join 会咋样?
如果是执行join的时候, t 线程已经结束了join不会阻塞,就会立即返回 => 保证线程的结束顺序

方法说明理解
public void join()等待线程结束无参数版本,“死等”,不见不散
public void join(long millis)等待线程结束,最多等 millis 毫秒指定一个超时时间(最大等待时间) ~~ 这种操作方式是更常见的 ~~ 死等很容易出问题
public void join(long millis, int nanos)同理,但可以更高精度不做注释

知识扩展

多线程是实现并发编程的基础方式.比较接近系统底层,使用起来不太友好
为了简化并发编程,也引入了很多更方便,更不容易出错的写法
比如,erlang(编程语言),引入actor 模型.
go,引入了csp模型
js, 使用回调的方式 => async await
python => async await
……

                                            |
更多推荐

【C++】特殊类的设计

文章目录1.设计一个类,不能被拷贝2.设计一个类,不能被继承3.设计一个类,只能在堆上创建对象3.设计一个类,只能在栈上创建对象4.创建一个类,只能创建一个对象(单例模式)饿汉模式懒汉模式1.设计一个类,不能被拷贝💕C++98方式:在C++11之前,想要一个一个类不被拷贝,只有将拷贝构造函数定义为私有,这样在类外就不

RIS 系列 TransVG++: End-to-End Visual Grounding with Language Conditioned Vision Transformer 论文阅读笔记

RIS系列TransVG++:End-to-EndVisualGroundingwithLanguageConditionedVisionTransformer论文阅读笔记一、Abstract二、引言三、相关工作3.1视觉定位两阶段方法单阶段方法视觉编码器的融合3.2Transformer视觉任务中的Transform

MFC-GetSystemFirmwareTable获取系统固件表

获取ACPI表格voidCgetSystemFirmwareTableDlg::OnBnClickedButton1(){//UINTbufferSize=GetSystemFirmwareTable('ACPI',0,NULL,0);//获取系统固件表/*【参数3和参数4为NULL和0,只是为了返回真实大小】这个函数

JUC第五讲:关键字synchronized详解

JUC第五讲:关键字synchronized详解在C程序代码中我们可以利用操作系统提供的互斥锁来实现同步块的互斥访问及线程的阻塞及唤醒等工作。在Java中除了提供LockAPI外,还在语法层面上提供了synchronized关键字来实现互斥同步原语,本文是JUC第五讲,将对synchronized关键字详细分析。文章目

FatFS文件系统在MCU上的应用

FatFS文件系统是单片机领域有名的一个文件系统,由于它的轻量级和兼容性,备受MCU开发者青睐。在实现如U盘文件读写,SD卡的文件读写等工作时,我们往往需要一个文件系统来支持我们的工作。特别在一些MCU应用中,文件系统的加入能明显改善系统交互的友好性。在这一篇中,我们就来讨论FatFS文件系统在STM32F4上的移植和

华为云认证考试包含哪些内容?

华为云计算认证考试包含哪些内容?华为云计算认证涵盖了hcia、HCIP、HCIE三个级别的认证。HCIA云计算方向只要考一门笔试,考试覆盖基础通识知识、虚拟化FusionCompute、桌面云FusionAccess、云计算发展趋势共四大模块知识点,包括云计算概述、服务器基础、存储技术基础、网络技术基础、操作系统基础、

华为云云耀云服务器实例使用教学

目录国内免费云服务器(体验)认识国内免费云服务器如何开通国内免费云服务器云耀云服务器HECSXshell远程连接云服务器更改安全组切换操作系统服务器详情HECS适用于哪些场景?网站搭建电商建设开发测试环境云端学习环境为什么选择华为云耀云服务器HECS国内免费云服务器(体验)云服务器_国内免费云服务器_免费云服务器_华为

00-从JAVA工程师的角度快速上手Python-基础语法

从JAVA工程师的角度快速上手Python-基础语法基础语法代码块和控制结构的表示变量和类型声明函数定义静态方法和类方法默认参数匿名函数函数式编程类型注解条件语句循环结构集合类型数组和列表列表切片字典操作迭代器生成器列表推导式和集合推导字符串操作函数调用和参数传递值传递方式对象传递方式模块和导入特殊方法(魔术方法)注释

StarRocks 社区:从初生到两周年的进化之路

2021年9月8日,StarRocks开源社区诞生。从第一天开始,我们怀揣着“打造世界一流的数据分析产品”的梦想,踏上了星辰大海的征途。两年间,StarRocks在GitHub上收获了5.4KStars,产品共迭代发布了90余个版本,288家市值超过10亿美元的头部用户在生产环境中上线运行。“不止步于极速”,StarR

如何使用Docker部署debezium来监控 MySQL 数据库

目录一、什么是Docker二、什么是debezium三、什么是MySQL四、如何使用Docker部署debezium来监控MySQL数据库一、什么是DockerDocker是一个开源的应用容器引擎,它让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows操作系统的机器上

Oracle系列十九:Oracle的体系结构

Oracle的体系结构1.物理结构2.内存结构2.1SGA2.2后台进程3.逻辑结构1.物理结构Oracle数据库的物理结构由参数文件、控制文件、数据文件和日志文件组成,用于存储和管理数据库的数据和元数据,每个文件都扮演着不可或缺的角色。参数文件用于配置数据库的初始化参数控制文件记录数据库的结构和状态信息数据文件存储了

热文推荐