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

进程

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

什么是进程

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

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

PBC

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

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

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

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

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

线程

线程是什么

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

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

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

线程是包含在进程内的,这样一个进程会有多个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 的常见构造方法

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

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

Thread 的几个常见属性

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

 

执行结果

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

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

线程控制

休眠当前线程sleep

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

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

运行结果:

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

线程中断interrupt

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

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

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

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

但是这种方法显然看起来有些简陋,而且如果使用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并不能直接中止线程,他的作用只是设置对象里的标志位,我们可以通过这个标志位来间接的中断线程,之所以这样是为了可以让程序猿有更大的操作空间来决定是否要中断线程。

举例:

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

补充:

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

运行结果:

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

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

线程等待join

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

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

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

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

线程状态

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

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

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

运行结果:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

食品企业销售管理体系升级的关键

盖世食品(股票代码:836826)是国家级农业产业化重点龙头企业,国家高新技术企业,拥有300种产品,1000SKU。从海洋蔬菜、营养菌菇、健康素菜到海珍味系列和鱼子系列,消费者经常可以从各大餐饮连锁店里吃到这家公司制作的凉…

前端开发(基础)

目录 一、Web前端项目初始化 环境准备 创建项目 前端工程化配置 引入组件库 开发规范 全局通用布局 基础布局结构 全局底部栏 动态替换内容 全局顶部栏 通用路由菜单 支持多套布局 请求 请求工具库 全局自定义请求 自动生成请求代码 全局状态管理 全局权限管…

跟着操作,解决iPhone怎么清理内存难题

在如今智能手机功能日益强大的时代,我们使用手机拍照、录制视频、下载应用、存储文件等操作都会占用手机内存。当内存空间不足时,手机运行会变得缓慢,甚至出现卡顿、闪退等现象。因此,定期清理iPhone内存是非常必要的。那么&#…

最新 taro v3 运行,报错 Error: [object Object] is not a PostCSS plugin 解决办法

报错如下: Error: [object Object] is not a PostCSS plugin 解决办法:pnpm install postcss -D 重新安装 postcss 依赖,重新运行即可。 结果:顺利运行

2000-2023年上市公司融资约束WW指数(含原始数据+计算结果)

2000-2023年上市公司融资约束WW指数(含原始数据计算结果) 1、时间:2000-2023年 2、来源:上市公司年报 3、指标:证券代码、证券简称、统计截止日期、是否发生ST或*ST或PT、是否发生暂停上市、行业代码、行业名称、上…

opengauss数据库兼容模式

一、官方说明 官方描述: 背景信息 初始时,openGauss包含两个模板数据库template0、template1,以及一个默认的用户数据库postgres。postgres默认的兼容数据库类型为O(即DBCOMPATIBILITY A ),该兼容类型下…

nginx的正向与反向代理

正向代理与反向代理的区别 虽然正向代理和反向代理都涉及代理服务器接收客户端请求并向服务端转发请求,但它们之间存在一些关键的区别: 正向代理: 在正向代理中,代理服务器代表客户端向服务器发送请求,并将服务…

怎么调整硬盘分区?让电脑运行更加高效!

硬盘分区是电脑存储管理的重要组成部分,合理的分区设置不仅能提高数据管理的效率,还能在一定程度上提升系统的运行性能。然而,随着使用需求的变化,我们可能需要对已有的硬盘分区进行调整。那么,我们该怎么调整硬盘分区…

PostgreSQL的学习心得和知识总结(一百四十八)|查看 PostgreSQL 17 中的新内置排序规则提供程序

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…

数码暴龙机(电波暴龙机)彩色复刻版!!| 使用Python、PySide6、pixilart自制windows桌面宠物

一、前言 数码暴龙机(电波暴龙机)是万代公司发售的一系列与《数码兽》系列相关的液晶玩具商品。这些产品融合了养成和对战元素,为玩家提供了一种虚拟养成和战斗的娱乐体验。也是很多人的童年回忆。最近在B站刷到讲解暴龙通关的教程和视频&…

ROS2 + 科大讯飞 初步实现机器人语音控制

环境配置: 电脑端: ubuntu22.04实体机作为上位机 ROS版本:ros2-humble 实体机器人: STM32 思岚A1激光雷达 科大讯飞语音SDK 讯飞开放平台-以语音交互为核心的人工智能开放平台 实现步骤: 1. 下载和处理科大讯飞语音模…

SQL Server的视图

SQL Server的视图 一、基础 SQL 视图(Views)是一种虚拟表,是基于 SQL 查询结果生成的。这些虚拟表可以包含来自一个或多个表的数据,并且可以像表一样查询;视图是一个表中的数据经过某种筛选后的显示方式,或…

Cornerstone3D导致浏览器崩溃的踩坑记录

WebGL: CONTEXT_LOST_WEBGL: loseContext: context lost ⛳️ 问题描述 在使用vue3vite重构Cornerstone相关项目后,在Mac本地运行良好,但是部署测试环境后,在window系统的Chrome浏览器中切换页面会导致页面崩溃。查看Chrome的任务管理器&am…

对话天润融通首席科学家:大模型的首要任务是为客户创造商业价值

2023年,AI大模型开启了企业数智化转型的新篇章。 不过前沿技术固然重要,但在增长见顶的存量背景下,先进技术带来的实用价值也尤为关键。 正如天润融通首席科学家田凤占所说:“现阶段最重要的是让大模型尽快和企业的业务相结合&a…

【Linux】进程间通信——消息队列和信号量

目录 消息队列(message queue) 信号量(Semaphore) system V版本的进程间通信方式有三种:共享内存,消息队列和信号量。之前我们已经说了共享内存,那么我们来看一下消息队列和信号量以及它们之间…

【嵌入式Linux】<总览> 网络编程(更新中)

文章目录 前言 一、网络知识概述 1. 网路结构分层 2. socket 3. IP地址 4. 端口号 5. 字节序 二、网络编程常用API 1. socket函数 2. bind函数 3. listen函数 4. accept函数 5. connect函数 6. read和recv函数 7. write和send函数 三、TCP编程 1. TCP介绍 2.…

新版本WPS不登录无法编辑的解决办法

原因分析:新版本的WPS因加入多种在线功能,建议登录账号获得更加体验 解决办法:首选第一种修改注册表后重启WPS,第二种仅作为临时满足工作需求,过一段时间会自动失效 方法一:键盘同时按下WINR键,…

【Python】基础语法(函数、列表和元组、字典、文件)

。一、函数 1、函数是什么 编程中的函数和数学中的函数有一定的相似之处。 数学上的函数,比如 y sin x,x 取不同的值,y 就会得到不同的结果。 编程中的函数是一段可以被重复使用的代码片段。 (1)求数列的和&…

MySQL索引特性(上)

目录 索引的重要 案例 认识磁盘 MySQL与存储 先来研究一下磁盘 扇区 定位扇区 结论 磁盘随机访问与连续访问 MySQL与磁盘交互基本单位 建立共识 索引的理解 建立测试表 插入多条记录 局部性原理 所有的MySQL的操作(增删查改)全部都是在MySQL当中的内存中进行的&am…

网友提问:HTML CSS JS很低级吗?

这话听起来就像有人在说披萨只是面团加奶酪,完全忽略了上面的美味配料和烘烤的艺术啊!HTML、CSS、JS这三位可是前端开发的铁三角,它们一点都不“低级”,反而相当关键。 HTML就像是房子的骨架,没有它,网页就…