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

进程

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

什么是进程

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

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

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{@Overridepublic 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() {@Overridepublic void run() {System.out.println("使用匿名类创建 Thread 子类对象");}
};
  •  使用匿名类创建 Runnable 子类对象 
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic 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.mzph.cn/pingmian/46841.shtml

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

相关文章

python实例练习00001:使用正则表达式获取文件内容

print(Hello World!) import re try:file = input(enter the file :)with open(file, r) as f:data = f.read() except FileNotFoundError:print(fthe file {file} does not exists:)# 定义正则表达式 pattern = r"num (\d+) name (\w+) class (\d+) age (\d+)" # 使…

Python 是一种用途广泛的编程语言,应用于各个领域

1. 网络和互联网开发: Python 拥有丰富的框架和库,使其成为 Web 开发的理想选择。 框架: Django 和 Pyramid 用于构建复杂的 Web 应用。Flask 和 Bottle 则适合轻量级应用和 API。 库: Python 标准库支持处理 HTML、XML、JSON 和电子邮件。此外,还有强大…

内容长度不同的div如何自动对齐展示

平时我们经常会遇到页面内容div结构相同页,这时为了美观我们会希望div会对齐展示,但当div里的文字长度不一时又不想写固定高度,就会出现div长度长长短短,此时实现样式可以这样写: .e-commerce-Wrap {display: flex;fle…

轻量级自适用商城卡密发卡源码(可运营)

一款全开源非常好看的发卡源码。轻量级自适应个人自助发卡简介,这是一款二次开发的发卡平台源码修复原版bug,删除无用的代码。所有文件全部解密,只保留后台版权信息内容。大家放心使用,可以用于商业运营。轻量级自适应个人自助发卡。 源码下…

R语言学习笔记7-列表

R语言学习笔记7-列表 列表(list)介绍空列表包含元素的列表嵌套列表访问列表元素添加新元素删除元素修改元素使用for循环遍历列表使用lapply遍历和操作列表使用sapply简化列表操作合并列表检查元素是否存在列表长度和名称操作将列表转换为其他数据类型列表与环境的交互列表在函数…

写python代码,怎么用工厂模式思维设计接口?

接口的好处 接口就是抽象方法,用来设计后架构,后端开发者和客户端调用者都可以使用这个接口规则同步写代码,客户端调用者(app、网页甚至时自动化接口测试)不用担心后端对接口的实现细节具体是什么样子的。直接去调用就…

QTimer::singleShot()

QTimer::singleShot() 是 Qt 框架中的一个静态函数,用于创建一个单次定时器事件。它的作用是在指定的时间间隔之后触发一个单次的定时器事件,然后停止定时器。 其函数原型为: static void QTimer::singleShot(int msec, const QObject *rec…

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

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

STM32学习和实践笔记(40):DS18B20温度传感器实验

1.DS18B20介绍 DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线(单总线)”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。 DS18B20温度传感器具有如下特点: 1、适应电压范围更宽,…

Spring 的核心注解

Spring框架使用了一系列的核心注解来支持其功能,以下是一些最常用的Spring注解: 1. Component: - 用于标记类为Spring组件,Spring容器会管理这些类的对象。 2. Service: - 特定于服务层的Component注解,表示一个服务组件。…

Qt 多窗体、复用窗口的使用

1.继承自QWidge的窗口的呈现,作为tabPage呈现,作为独立窗口呈现 2.继承自QMainWindow的窗口的呈现,作为abPage呈现,作为独立窗口呈现 1. 继承自QWidge的窗口的呈现 1.1 作为tabPage呈现 void MutiWindowExample::on_actWidgetI…

分项加载页面统计数据

我们在做一个统计页面时,原来大约有1000万左右的数据进行查询,还可以接受,但是随着业务量的增大,目前有3000多万的数据来统计,一次统计出查询结果就很慢很慢,有时候会出现超时异常。 为了解决这个问题&…

云计算数据中心(一)

目录 一、云数据中心的特征二、云数据中心网络部署(一)改进型树结构(二)递归层次结构(三)光交换网络(四)无线数据中心网络(五)软件定义网络 一、云数据中心的…

Briefcase:将Python项目转化为多平台应用的利器

文章目录 引言官网链接原理基础使用安装 Briefcase初始化项目构建应用创建应用包 高级使用应用程序配置和定制化与打包工具的集成自动处理依赖关系 优缺点优点缺点 总结 引言 Briefcase 是一个功能强大的工具,主要用于将 Python 项目转化为多种平台的独立本地应用。…

java 项目使用 acitiviti 流程引擎中的人员设置

学习目标: 目标 [ ]了解 java 项目使用 acitiviti 流程引擎中的人员设置 知识小记: - [x] 1、人员选择说明 - [x] 2、分配任务候选人 任务的候选人是指有权限对该任务进行操作的潜在用户群体,这个用户群体有权限处理(处理、完成)该任务…

最多可以派出多少支球队

最多可以派出多少支球队 解决“最多可以派出多少支球队”的问题需要准确理解题目要求,选择合适的算法(如贪心算法和双指针技术),并注意对原始数据进行适当的预处理(如排序)。在编程实现过程中,有…

MySQL索引重要知识点

1.什么是索引? 索引在项目中是比较常见的,它是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的I0成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低…

Vue2中的进度条案例

v-bind对于样式控制的增强--操作style 语法&#xff1a; :style “样式对象” 适用于某个具体属性的动态设置 <div class"box" :style"{css属性名1:css属性值,css属性名2:css属性值}"></div> <!DOCTYPE html> <html lang"en&…

java.sql.SQLException: Unknown system variable ‘query_cache_size‘【Pyspark】

1、问题描述 学习SparkSql中&#xff0c;将spark中dataframe数据结构保存为jdbc的格式并提交到本地的mysql中&#xff0c;相关代码见文章末尾。 运行代码时报出相关配置文件错误&#xff0c;如下。 根据该报错&#xff0c;发现网络上多数解决方都是基于java开发的解决方案&a…

uniapp字符串转base64,无需导入依赖(多端支持)

使用示例 import { Base64Encode, Base64Decode } from "@/utils/base64.js" base64.js const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";export const Base64Encode = (text)