《javeEE篇》--多线程(1)

news2024/11/26 19:43:01

进程

在讲线程之前我们先来简单了解一下进程

什么是进程

进程是操作系统对一个正在运行的程序的一种抽象,又或者说,可以把进程看作程序的一次运行过程(通俗的讲就是跑起来的程序)。

而且在操作系统内部,进程是资源分配的基本单位

PCB

PCB的中文翻译是进程控制抽象,在计算机内部要管理任何现实事物,都需要将其抽象成一组有关联的、互为一体的数据。PCB就相当于是对进程的抽象,里面包含了描述一个进程的各种属性,每一个PCB对象就代表着一个进程。

在操作系统中,会有很多进程那么,操作系统对这些进程进行管理,管理的方法是先描述,使用PCB表示出进程的各个属性,后组织,使用数据结构如线性表,搜索树把这些PCB给串起来

 PCB中有一些比较重要的属性

  • pid(进程标识符):用来区分各个进程,是进程的唯一标识符
  • 内存指针:表示进程所在的内存空间,换言之是进程所持有的内存资源
  • 文件描述符表:表示内存所持有的硬盘资源
  • 状态:进程的状态有很多,常见的有运行状态,就绪状态和阻塞状态,运行状态就是进程正在运行,就绪状态就是进程正在准备运行,阻塞状态就是,进程中断,正在等待事件的完成
  • 优先级:不同的进程往往优先级不同,优先级不同往往给进程分配的资源不同,比如当你的电脑一边在打游戏,一边在挂着QQ,QQ的消息可以晚收到一两秒,但是如果游戏里每一个动作都有一两秒的延迟,那么这个游戏就没法打了,所以此时操作系统会给游戏分配更多的资源,不过这个状态在用户眼里往往是不明显的。
  • 上下文:进程执行时寄存器中的数据
  • 记账信息可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等等。

//并发:当我们所要执行的进程太多,cup的核心数不够,就需要让这些进程在cpu上轮流执行,只要轮的够快,在宏观上看起来就像是这些进程在同时执行

线程

线程是什么

我们可以先看一个例子假如一片大空地上有一个厂房,有一天厂长想要加大生产,那么就需要新建工厂,这时有两个选择一个是,再租一篇地皮来建造工厂,另一种是在原有的空地上再建一个

0c2820939ec74f8e9537b863619eb478.png

显然,选择第一种会更加节省开销。

由于进程的创建,销毁等操作开销较大,所以人们提出了线程的概念,进程就相当于是空地,线程就是工厂。线程相当于是进程的一个执行路径,也可以叫做“轻量级的进程”。同一个进程中的线程会共享进程所申请到的资源,所以创建线程时不需要再额外申请空间,这样就大大降低了调度的成本。进程有的一些属性,线程往往也具有。

线程是包含在进程内的,这样一个进程会有多个PCB同时表示,每个PCB就用来代表一个线程,每个线程都有自己的状属性(状态,优先级,上下文......),每个线程都可以独立的去CPU上调度执行,这些PCB共用了同样的内存指针和文件描述表,这就使创建线程(PCB)就不需要重新申请空间了,就大大提高了创建和销毁线程的效率。

 线程和进程的区别

  • 进程是资源分配的基本单位,线程是执行调度的基本单位
  • 进程包含线程,一个进程至少会有一个线程,这个至少的线程叫做主线程
  • 同一个进程的线程之间,共用同一份资源(内存+硬盘),省去了申请资源的开销
  • 进程和进程之间是互相独立的,进程和线程之间,可能会互相影响
  • 进程和线程都是用来实现并发场景的,但是线程比进程更加轻量,更高效

线程的创建

方法一

继承Thread,重写run:

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装

  1. 首先定义一个类(这里我的类名为MyThread),这个类需要继承Thread
  2. 然后需要重写run方法,run方法内部就是我们要执行的线程代码
  3. 最后启动线程
class MyThread extends Thread{
    public void run(){
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
                //因为父类的抽象方法没有抛出异常,所以这里只能try catch
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        while(true){
            System.out.println("hello world");
            Thread.sleep(1000);
            //这里不是继承自父类
        }
    }
}

 注意此处我们调用的不是run方法而是start方法,如果只是单纯的调用run方法是不会启动线程的,run方法不会分配新的分支栈。

start方法的作用是,启动一个分支栈,通过调用系统的API,在JVM中创建一个新的栈空间,来在系统内核中创建线程,而run方法就只是单纯的描述一下这个线程要执行啥内容,run方法会在start方法创建好线程,线程启动成功之后自己被调用。

方法二

实现Runnable接口,重写run

  1. 定义一个类实现Runnable接口
  2. 实现run方法
  3. 构建Thread对象,将创建的Runnable对象作为参数传入
  4. 启动线程
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("hello runnable");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        while (true){
            System.out.println("hello main");
                Thread.sleep(1000);
        }
    }

}

//这里Runnable表示一个可执行的任务,它将这个任务交给线程负责执行 

方法三

匿名内部类

可以不用单独创建一个类直接使用匿名内部类

  •  使用匿名类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
   }
};
  •  使用匿名类创建 Runnable 子类对象 
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
   }
});
  • 使用lambda 表达式创建 Thraed子类对象

 lambd表达式相当于是匿名内部类的替换写法,这种方法可以快速方便的就创建出一个线程

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

//lambda表达式本质上,是一个匿名函数(没有名字的函数,用一次就完了),主要来实现“回调函数”的效果

Thread 类及常见方法 

Thread 的常见构造方法

8090e419b1fe4f09a4ecc510f02e463d.png

 我们在创建线程的时候可以给线程取名字,线程取名字不会影响到线程的正常运行,只是方便之后的区分,可以在jdk给我们提供的工具jconsole.exe查看

//还可以使用setName方法手动命名

Thread 的几个常见属性

e931ca623c13423caf6741c60303402a.png

//在默认情况下一个线程是前台线程,一个Java进程中如果前台线程没有执行结束,此时整个进程是一定不会结束的,后台线程(守护线程),不结束不会影响到整个进程的结束

 af657da915f248108f4186582ae5782e.png

执行结果

cec7b66b3c004284a1390912bd4f15da.png

改成后台线程之后主线程飞快地执行完了,此时没有其他前台线程了,于是进程结束,t线程来不及执行就完了

使用isAlive()可以知道当前线程是否在执行,如果在执行就会返回true,否则返回false

线程控制

休眠当前线程sleep

sleep可以让当前线程停止一定之间

b4eec42cbffd4313be85b085b3c581db.png

//因为父类的抽象方法没有抛出异常,所以这里只能try catch

运行结果:

0bb94582c8c84e169804a513d11fe4e5.png

但是要注意,因为线程的调度是不可控的,所以,这个方法只能保证实 际休眠时间是大于等于参数设置的休眠时间的。

b63a7119317a4648a2f2efe90ee60dee.png

fdce1744139249eda0734d2bdbff19d4.png

线程中断interrupt

常见的线程中断方式有两种

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

1.使用自定义的变量来作为标志位

我们定义一个当作线程中断标志的变量,通过其他线程对这个变量的修改,来实现对该线程的中断 

494391cba8354ca9bdc565b1049e76fe.png

但是这种方法显然看起来有些简陋,而且如果使用lambda表达式创建线程会比较麻烦,而且如果线程内部在sleep的时候,主线程修改变量,新线程内部不能及时响应

lambda表达式会自动捕获方法内,之前出现的变量

lambda表达式内使用的标志,必须是final或者常量

 2.使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.

 在Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记,叫做中断标志位。

  • interrupt方法可以中断对象关联的线程,如果此时线程处在阻塞状态下比如wait/join/sleep,interrupt就会报出一个异常,否则将会设置标志位(把Thread对象内部的标志位设置为true)
  • Thread.interrupted() 可以判断,当前线程的中断标志位是否被设置,如果被设置就会返回true否则返回false,并且在调用结束后,会清除标志位,比如当interrupt将标志位设置为true时,Thread.interrupted()就会先返回true然后再将刚刚被设置的标志位清除(将标志位再变成false),就好像一个按钮,按一下就会会弹起来。
  • Thread.currentThread().isInterrupted()也可以判断当前对象的线程的标志位是否被设置,但是调用后不清除标志位,比如当interrupt将标志位设置为true,Thread.currentThread().isInterrupted()只会返回一个true之后什么也不会做了,就像一个拉杆,拉一下会持续有效。

 //注意interrupt并不能直接中止线程,他的作用只是设置对象里的标志位,我们可以通过这个标志位来间接的中断线程,之所以这样是为了可以让程序猿有更大的操作空间来决定是否要中断线程。

举例:

a67fe02a91f94f298aabab14c1cacce9.png

我们刚刚有说到当调用interrupt时,如果此时线程处在阻塞状态下比如wait/join/sleep,interrupt就会报出一个异常,所以当出现interruptException时,要不要直接结束线程,或者执行一段代码后再结束比如收尾工作,又或者是直接忽略这个异常,就取决于我们catch中的写法了

补充:

currentThread()的作用是那个线程调用这个方法,就会返回那个线程的对象,所以Thread.currentThread()就相当于,获取到当前的线程实例,在这里就是thread

运行结果:

879683a00e494255ac6e85cbf5c65ce0.png

运行后我们发现线程并没有停止,刚刚我们说过Thread.currentThread().isInterrupted()不会清理标志位,按理来说当执行interrupt时标志位被改,Thread.currentThread().isInterrupted()返回true,线程应该执行结束了呀?

上述结果异常确实是出现了,sleep也确实被唤醒了,但是线程任然在工作。在interrupt唤醒线程之后,此时seelp方法抛出异常,在抛出异常的同时还会顺带自动清理刚才设置的标志位,所以这里标志位并不是被Thread.currentThread().isInterrupted()清理的,这样就使interrupt的“设置标志位”的效果看起来就好像没生效一样。

线程等待join

线程等待就是,让一个线程等待另一个线程执行结束,再继续执行,本质上就是控制线程结束的顺序。利用join实现线程等待

5543126726f44acf9920e0272273b124.png

t.join意思就是,当前线程等待t线程执行结束之后才可以执行,那个线程调用的join,那个线程就需要等待

  • 如果t线程正在运行中,此时调用join的线程main就会阻塞,一直阻塞到t线程执行结束为止
  • 如果t线程已经执行结束,此时调用join线程,就会直接返回了,不会涉及阻塞

//但是有时如果让线程一直等待下去,也不太合适所以我们往往会设定一个,最大等待时间,如果超出这个时间就会停止等待

4f156a56221e48e9a236910150f371a2.png

线程状态

线程的状态其实,是一个枚举类型,可以通过sout打印。

  • NEW:Thread对象已经有了,但是线程还没有启动(start方法还没调用)
  • RUNNABLE:就绪状态,线程已经在CPU上执行了/线程线程正在等待CPU调度
  • TIMED_WAITING:阻塞状态,由于sleep这种固定时间的方式发生的阻塞
  • WAITING:阻塞状态,由于wait这种不固定时间的方式产生的阻塞(会在之后的篇章中讲到)
  • BLOCKED:阻塞,由于锁竞争导致的阻塞(会在之后的篇章中讲到)
  • TERMINATED:对象还在,内核中的线程以及没了(线程执行完了)

 我们可以通过getState来获取当前状态

e311f88a45834d03b7512e5586a19e72.png

运行结果:

5dc2f733e2974f47af9f32ea6ee08d6c.png

以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

下一篇博客博主将分享有关线程安全以及锁等知识,还希望多多支持一下!!!😊

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1929871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学生基本信息界面(MFC)

本文将引用MFC常用控件,写一个学生基本信息界面,最后将统计结果显示在提示框中,运行效果如下: 1.新建基于对话框的MFC项目,布局对话框,修改相应控件ID并绑定变量 注意:第一个单选控件的group属性 3.在构造…

《算法笔记》总结No.7——二分(多例题详解版)

一.二分查找 目前有一个有序数列,举个例子,假设是1~1000,让我们去查找931这个数字,浅显且暴力的做法就是直接从头到尾遍历一遍,直到找到931为止。当n非常大,比如达到100w时,这是一个非常大的量级…

获取欧洲时报中国板块前新闻数据(多线程版)

这里写目录标题 一.数据获取流程二.获取主页面数据并提取出文章url三.获取文章详情页的数据并提取整体代码展示 一.数据获取流程 我们首先通过抓包就能够找到我们所需数据的api 这里一共有五个参数其中只有第一个和第五个参数是变化的第一个参数就是第几页第五个是一个由时…

论文翻译 | Successive Prompting for Decomposing Complex Questions 分解复杂问题的连续提示

摘要 回答需要做出潜在决策的复杂问题是一项具有挑战性的任务,尤其是在监督有限的情况下。 最近的研究利用大型语言模型(LMs)的能力,在少量样本设置中通过展示如何在单次处理复杂问题的同时输出中间推理过程,来执行复杂…

【自学安全防御】二、防火墙NAT智能选路综合实验

任务要求: (衔接上一个实验所以从第七点开始,但与上一个实验关系不大) 7,办公区设备可以通过电信链路和移动链路上网(多对多的NAT,并且需要保留一个公网IP不能用来转换) 8,分公司设备可以通过总…

Jdk8 Idea Maven Received fatal alert: protocol_version

问题描述 使用idea开发工具,maven加载项目依赖时,出现错误: Could not transfer artfact xxxxxxx from/to maven-dep-repos https://XXXXXXX: Received fatal alert: protocol_version初步思路 用关键字protocol_version 去检索&#xff0…

Schematics,一个牛逼的python库用于数据验证和转换的库

目录 什么是Schematics? 为什么使用Schematics? 安装Schematics 定义模式 验证数据 自定义验证 转换数据 结语 什么是Schematics? 在Python的世界中,Schematics是一个用于数据验证和转换的库。它通过定义数据结构的模式(…

30秒学会UML-功能类图

目录 1、类图本体 三部分 修饰符 2、类与类直接关系 泛化关系 实现关系 简单关联关系 依赖关系 组合关系 聚合关系 1、类图本体 三部分 第一层:类名第二层:成员变量(类的属性)第三层:函数方法(类…

PX4 运行 make px4_sitl_default gazebo 报错

报错原因:最开始我把依赖一直都是在base环境下安装的,没有conda deactivate,而pip install的东西应该装在系统环境,不能装在base环境下,sudo apt 是装在系统环境的 1.检查ros 用鱼香ros安装 wget http://fishros.…

SSL证书续费

讲解下域名证书如何续费(以阿里云为例) ‍ 提醒 一般云服务器厂商,都会提前和你一个月左右通知(邮件、短信等),例如: 尊敬的 xxx:您域名 www.peterjxl.com 使用的 SSL 证书 xxxxx…

Linux编程(通信协议---udp)

UDP(用户数据报协议)是一种无连接的网络协议,主要用于快速传输数据。以下是UDP协议的一些主要特点: 1. **无连接**:UDP是无连接的协议,这意味着在数据传输之前不需要建立连接。每个UDP数据包都是独立的&am…

数据库操作太复杂?Python Shelve模块让你轻松存储,一键搞定!

目录 1、基本操作入门 📚 1.1 安装Shelve模块 1.2 创建与打开Shelve文件 2、存储与读取数据 🔐 2.1 写入键值对 2.2 读取存储的数据 3、高级功能探索 🧭 3.1 使用Shelve迭代键和值 3.2 键的管理:添加、删除与更新 4、异…

minishell

今天完成了minishell的制作 项目需求: 1. 获取终端用户输入的命令,并输出相应的执行结果。 touch cp mv ls ls -a ls -l mkdir rmdir pwd cd ln ln -s exit ---------…

鸿蒙开发HarmonyOS NEXT (四) 熟悉ArkTs (下)

一、动画 1、属性动画 animation,可以通过配置动画时间duration等参数,实现移动时的平滑过度 写了个小鱼游动的小案例 Entry Component struct ActionPage {State fish: Resource $r(app.media.fish_right) //小鱼图片State fishX: number 200 //初始化小鱼横坐…

Day07-员工管理-上传下载

1.员工管理-导出excel 导出员工接口返回的是二进制axios配置responseType为blob接收二进制流文件为Blob格式按装file-saver包,实现下载Blob文件npm install add file-saver导出员工excel的接口 (src/api/employee.js) export function exportEmployee(){return req…

【区块链 + 智慧政务】涉税行政事业性收费“e 链通”项目 | FISCO BCOS应用案例

国内很多城市目前划转至税务部门征收的非税收入项目已达 17 项,其征管方式为行政主管部门核定后交由税务 部门征收。涉税行政事业性收费受限于传统的管理模式,缴费人、业务主管部门、税务部门、财政部门四方处于 相对孤立的状态,信息的传递靠…

【Diffusion学习】【生成式AI】Diffusion Model 原理剖析 (2/4) (optional)【公式推导】

文章目录 影像生成模型本质上的共同目标【拟合分布】Maximum Likelihood Estimation VAE 影像生成模型本质上的共同目标【拟合分布】 Maximum Likelihood Estimation VAE

图片服务器是什么?常见的图片服务器是哪几种?图片服务器的要求是什么?

什么是图片服务器 图片服务器,顾名思义就是专门用于处理图片的服务器,向外提供图片的上传,下载,图片展示等服务 为什么我们要使用专门的服务器处理图片 图片的数据量比文字展示高得多,图片的上传下载展示一系列操作…

Linux进程——进程优先级与僵尸进程孤儿进程

文章目录 僵尸进程变成僵尸状态的过程 孤儿进程进程优先级如何修改进程优先级为什么优先级有范围 僵尸进程 僵尸状态进程本质上就是死亡状态 在进程死亡之后,不会直接对进程进行释放,而是先会处理一些后事 进程在结束退出的时候,也会有一些…

介绍 Elasticsearch 中的 Learning to Tank - 学习排名

作者:来自 Elastic Aurlien Foucret 从 Elasticsearch 8.13 开始,我们提供了原生集成到 Elasticsearch 中的学习排名 (learning to rank - LTR) 实现。LTR 使用经过训练的机器学习 (ML) 模型为你的搜索引擎构建排名功能。通常,该模型用作第二…