「JavaEE」线程

🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!

线程

  • 🍉线程
    • 🍌多线程
    • 🍌线程与进程的联系&区别
    • 🍌多线程编程
    • 🍌创建线程
    • 🍌Thread 其他重要属性与方法
  • 🍉操作系统内核

🍉线程

上篇文章中我们介绍了进程,但实际上在Java中是不太鼓励“多进程编程”的,大多数时候我们使用的是线程

进程可以很好地解决并发编程这样的问题,但是在一些特定的情况下,它的表现不尽人意。比如有些场景需要频繁创建和销毁进程,此时使用多进程编程的话,系统开销就会很大

开销是哪来的呢?一个进程刚启动时,需要把依赖的代码和数据从磁盘加载到内存中。而从系统分配一块内存并非一件易事,一般申请内存的时候需要先指定一个大小,然后系统内部把各种大小的空闲内存通过一定的数据结构组织起来,这个过程需要一定的时间开销

而线程就是解决上述问题的方案
线程也可以称为轻量级进程,它在进程的基础上做出改进

前面我们说一个进程是由 PCB 来描述的;其实 PCB 也可以用来描述一个线程

PCB 中有个属性,叫内存指针。多个线程 PCB 的内存指针指向的是同一个内存空间
这意味着在创建第一个线程的时候就需要从系统分配资源。后续的线程就不必再分配,直接共用前面那份资源就 ok 了
除了内存,文件描述符表也是多个线程共用一份的(共享经济属于是)

在这里插入图片描述
当然也不是随便两个线程都能共享资源,我们把能够共享资源的线程分成组,称为线程组
而一个进程可以有多个PCB,这就意味着这个进程包含了一个线程组(多个线程)


🍌多线程

多线程是指在同一个进程中同时运行多个线程,每个线程可以执行独立的任务并且能够同时运行,这样同时执行多个任务可以提高程序的性能和响应速度

但是线程也不是越多越好,当线程数量太多的时候,线程之间就会相互竞争 CPU 的资源(因为 CPU 调度执行线程的数量是有限的),导致不仅不会提高效率,还会增加调度的开销

而且多线程还有一个问题,就是线程之间可能会起冲突,这就会导致代码中出现一些逻辑上的错误(这是后面要讨论的线程安全问题);一个线程如果抛出异常,并且没有处理好,就可能导致整个进程终止


🍌线程与进程的联系&区别

  1. 进程是包含线程的
  2. 每个线程是一个独立的执行流,可以执行一些代码,并且单独参与到 CPU 调度中
  3. 每个进程有自己的资源,进程中的线程共享这一份资源(内存空间、文件描述符表等)

由2和3可以得出:进程是资源分配的基本单位;线程是调度执行的基本单位

  1. 进程与进程之间不会相互影响,但是线程会(线程安全问题)。如果同一个进程中的某个线程抛出异常,可能会影响到其他线程,甚至会导致整个进程中所有线程都异常终止
  2. 线程不是越多越好,差不多就得了,如果线程太多了,调度开销可能非常明显

🍌多线程编程

在Java中,写代码的时候推荐使用多线程并发编程,系统提供了多线程编程的 api,而Java标准库把这些 api 封装好了,在代码中就可以直接使用,比如Thread类

打开 idea,我们先写一个 MyThread 类继承 Thread,并写一个 run 方法:
在这里插入图片描述

这个 run 方法就类似于 main 方法,是一个 Java 线程的入口方法。一个进程中至少有一个线程,这个进程的第一个线程,称为主线程,所以 main 方法也就是主线程的入口方法(因为一个进程肯定要有一个 main 方法)

然后还有一点,就是 run 是不需要我们手动调用的,它会在合适的时机(线程创建好之后)被 jvm 自动调用执行(这样的函数称为回调函数)
我们前面所学的优先级队列,往它插入个对象,需要先指定比较规则,这就要实现 Comparable 或者 Comparator 接口,分别重写 compareTo 和 compare 方法,这两个也属于回调函数

说回正题,现在要搞一个线程,就是要让这个线程执行一些代码。显然,标准库自带的 run 肯定是不知道我们的需求,这就需要我们进行拓展(Thread 类有很多属性、方法,大部分都可以复用,只用把需要拓展的进行拓展即可)

我们重写一下 run 方法,并创建一个线程:

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello thread");
    }

    public static void main(String[] args) {
        //根据刚才的类,创建出实例
        Thread t = new MyThread();
        //调用 Thread 类的 start 方法,才会真正调用系统的 api,在系统内核中创建线程(线程就会执行上面写好的 run 方法)
        t.start();
    }
}

那么现在上面的代码就有两个线程:t 线程和 main 线程
每个线程都是一个独立的执行流,它们都能独立去 CPU 上调度执行
以上面代码为例,现在稍微修改一下,两个线程都加个死循环:

public class MyThread extends Thread{
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000); //控制隔一秒才打印,降低循环速度,避免循环跑起来的时候跑太快,导致 CPU 占用率比较高
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        while(true) {
            System.out.println("hello main");
            sleep(1000);
        }
    }
}

运行结果如下图

在这里插入图片描述

可以看到两个循环都在执行,因为这两个线程就是两个独立的执行流
具体的执行流程就是:在 main 方法中调用 start 创建线程之后“兵分两路”,一路沿着 main 方法继续执行,打印“hello main”,另一路进入到线程的 run 方法,打印“hello thread”

然后有个需要注意的点,当有多个线程的时候,这些线程执行的先后顺序是不确定的,这是因为操作系统内核中有一个“调度器”模块,这个模块的实现了一种类似“随机调度”的效果。所谓的随机调度,指的是:
①一个线程被调度到 CPU 上执行的时机是不确定的
②一个线程从 CPU 上下来,给其他线程让位的时机也是不确定的

这两点其实归因于线程执行采用抢占式执行的机制:操作系统根据优先级等参数来决定何时中断当前线程,并切换到其他线程
这个机制使得多线程程序可以更好地利用 CPU 资源,增加并发性和吞吐量,但是也带来了线程安全问题

还是以上面的代码为例,别看是先进入 main 方法就以为是先执行 main 线程,其实它和 thread 谁先谁后是不确定的


🍌创建线程

上面介绍了一种创建线程的方式,不过那不是主流的方式。我们通常使用 lambda 表达式创建一个线程

        Thread t1 = new Thread(()-> {
            System.out.println("hello thread");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();

这个写法相当于实现 Runnable 接口并重写 run 方法,lambda 代替了 Runnable 的位置


🍌Thread 其他重要属性与方法

方法

Thread(Runnable target) //使用 Runnable 对象创建线程对象
Thread(String name) //创建线程对象并命名
Thread(Runnable target,String name) //使用 Runnable 对象创建线程对象并命名

我们自己创建的线程默认是按照 Thread-0 1 2……命名的,给不同线程起不同名字对于线程的执行没有影响,主要是方便调试。此外,线程之间的名字是可以重复的,但名字别乱起,最好要有一定的描述性

属性

属性获取方法
ID(jvm自动分配的身份标识,会保证唯一性)getID()
名称getName()
状态(进程有就绪状态,阻塞状态等,线程也有状态)getState()
优先级getPriority()
是否为后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

(为了让表格看上去不会冗杂,一些属性的说明放到这下面讲)
优先级:在 Java 中,由于系统是随机调度线程的,所以对线程设置优先级的效果不是很明显

后台线程:后台线程的运行不会阻止进程结束,与后台线程相对,还有前台线程,前台进程的运行,会阻止进程结束(注意这里的后台和我们平时手机的“杀后台”不是一回事)

我们来演示一下前台线程,只需把刚才代码中 main 线程的死循环去掉:

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

在这里插入图片描述

进程执行后,会一直打印,只有当我们停止进程后,出现红色方框中这句话,才表示进程结束
这是因为我们创建的线程默认是前台线程,即使 main 已经执行完了,只要前台线程没执行完,进程就不会结束

然后我们把 t 改为后台线程:

        t.setDaemon(true); //设为 true 就是改为后台,注意 setDaemon 一定要写在start前面
        t.start();

在这里插入图片描述
可以看到什么都没打印,进程就结束了

isAlive:它表示内核中的线程(PCB)是否还存在。如果线程已经启动并且还没有终止,那就会返回 true;反之返回 false

Java 代码中定义的线程实例虽然表示一个线程,但是这个实例本身的生命周期和内核中 PCB 的生命周期是不完全一样的

Thread t = new Thread(()-> {
	...
})

比如现在创建了 t 实例,由于线程还没有 start,所以此时 isAlive 的结果就是 false


🍉操作系统内核

我们在上文中多次提到“内核”这个概念

内核是操作系统中最核心部分的功能模块,它负责管理硬件,给软件提供稳定的运行环境
操作系统的内存空间分为两块:内核空间(内核态)和用户空间(用户态)

为什么要划分出这两个空间呢?主要是为了稳定,防止应用程序把硬件设备或软件资源搞坏了。系统封装了一些 api,这些 api 都是一些合法的操作,应用程序只能调用这些 api,这样就不至于对系统以及硬件设备产生太大危害

我们平时运行的普通应用程序,比如 idea、谷歌、微信……都是在用户态运行的。这些程序有时候需要针对一些系统提供的软硬件资源进行操作,这些操作都不是应用程序直接操作的,需要调用系统提供的 api,然后在内核中完成这些操作

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

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

相关文章

基于springboot实现英语知识应用网站系统项目【项目源码+论文说明】

基于springboot实现英语知识应用网站系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了英语知识应用网站的开发全过程。通过分析英语知识应用网站管理的不足,创建了一个计算机管理英语知识应…

vue 常用的日历排班,带农历显示组件(2024-04-16)

显示当前月日历组件,里面带农历或节日显示 后面可以丰富一些国家法定节假期的业务需求 代码 js-calendar.js 文件 var lunarInfo [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, //1900-19090x04ae0, 0x0a5b6, 0…

【VIC水文模型】模型输入/输出参数简介

VIC水文模型输入参数简介 输入数据1.1 背景参数1.2 植被分类及属性配置1.3 土壤数据库制作1.4 气象数据库制作1.5 区域控制文件1.6 汇流文件制作 输出数据参考 VIC水文模型是基于空间分布网格化的分布式水文模型。通过将研究区域网格化,分别考虑每个计算网格内裸土和…

ThreadLocal和ThreadLocalHashMap

请直接百度详细介绍 -------------------------------------------------------------------------------------------------------------------------------- 1.ThreadLocalMap是Thread类里的一个局部变量 2.ThreadLocalMap是ThreadLocal类里的一个静态内部类, 3.ThreadL…

Java springboot使用EasyExcel读Excel文件,映射不到属性值,对象属性值都是null

如果你的类上有这个注解,去掉火或注释掉就可以了 Accessors(chain true)解决方法

聊聊最近两星期的学习吧!

今天是4月14号。 自从我3月份回到学校之后,我每天都有记录自己的学习时长。今天晚上,我在复盘我自己学习时长的时候,我发现,在整个四月份,我平均每天的有效学习时长只有6h,而且到今天为止,整个四…

【深度学习实战(5)】使用仿射变换来实现自己的letter_box操作

一、letter_box 深度学习模型输入图片的尺寸为正方形,而数据集中的图片一般为长方形,粗暴的resize会使得图片失真,采用letterbox可以较好的解决这个问题。该方法可以保持图片的长宽比例,剩下的部分采用灰色填充。 二、代码 本例…

美易官方:若FED再不降息,美国经济将硬着陆

有关美股市场可能重演2022年熊市的担忧逐渐升温。阿波罗全球管理公司首席执行官马克罗文在接受采访时发出警告,如果美联储(FED)不采取降息措施,美国经济将面临硬着陆的风险。这一言论引发了市场对未来经济走势的广泛关注。 在周二…

​面试经典150题——翻转二叉树

1. 题目描述 2. 题目分析与解析 分析题目可以看出,其实就是从下到上的左右节点互换操作,其实上也是可以进行递归操作的,这是因为每一个子操作和父操作都是一样的方式。 解题思路: 空树情况处理: 首先检查根节点是否…

视频批量高效剪辑,支持将视频文件转换为音频文件,轻松掌握视频格式

在数字化时代,视频内容日益丰富,管理和编辑这些视频变得愈发重要。然而,传统的视频剪辑软件往往操作复杂,难以满足高效批量处理的需求。现在,一款全新的视频批量剪辑神器应运而生,它支持将视频文件一键转换…

02_对象树

#include "mypushbutton.h" #include <QDebug>MyPushButton::MyPushButton(QWidget *parent): QPushButton(parent) {qDebug()<<"我的按钮类构造调用"; }MyPushButton::~MyPushButton() {qDebug()<<"我的按钮类析构调用"; }交…

初识数据库与数据库管理系统

实体的概念与数据库 实体(对象): 客观存在的事物都是实体实体数据的存储要求: 必须按照一定的分类和规律存储数据库: 专门用于存储这些实体的信息的数据集合数据库的特点: 海量存储数据&#xff0f;数据检索非常方便保持数据信息的一致&#xff0f;完整&#xff0f;并实现数据…

【计算机毕业设计】家庭食谱管理系统产品功能介绍——后附源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法

机器人视觉软件实现目标检测通常借助深度学习技术和计算机视觉算法。以下是一般而言的目标检测实现步骤&#xff1a; 1、数据收集与标注&#xff1a;首先需要收集包含目标物体的大量图像数据&#xff0c;并对这些图像进行标注&#xff0c;标注出目标物体的位置和类别信息。这些…

第十五届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组(基础题)

试题 C: 好数 时间限制 : 1.0s 内存限制: 256.0MB 本题总分&#xff1a;10 分 【问题描述】 一个整数如果按从低位到高位的顺序&#xff0c;奇数位&#xff08;个位、百位、万位 &#xff09;上 的数字是奇数&#xff0c;偶数位&#xff08;十位、千位、十万位 &…

模仿SpringSecurity配置文件的写法对mybatisPlus查询方法的改造

使用mybatisPlus查询数据的传统流程是&#xff1a;Autowired mapper对象。new Wrapper 一通乱set Wrapper ,select xxx。但实际开发中&#xff0c;还有很大的改进空间&#xff0c;一是一些脆弱的参数设置有多处&#xff0c;得不到妥善维护&#xff0c;二是代码编写丑陋难看。因…

Fluke ADPT连接器(隔离版)----发布2

代替手工记录、记录后在整理的麻烦&#xff0c;轻点鼠标&#xff08;单次采集、自动时间间隔采集自由选择&#xff09;即可完成&#xff0c;测试数据导出图片、导出数据到EXCEL文件随意选择&#xff1b; 所需设备&#xff1a; 1、Fluke ADPT连接器&#xff1b;内附链接 ● …

ArtCoder——通过风格转换生成多元化艺术风格二维码

简介 ArtCoder能够从原始图像&#xff08;内容&#xff09;、目标图像&#xff08;风格&#xff09;以及想要嵌入的信息中&#xff0c;生成具有艺术风格的二维码。这一过程类似于通常的图像风格转换&#xff0c;但特别针对二维码的特点进行了优化和调整。 通过这种方法&#…

芯片设计围炉札记

文章目录 语言Verilog 和 VHDL 区别 芯片验证 语言 System Verilog的概念以及与verilog的对比 IC 设计软件分析 Verilog 和 VHDL 区别 Verilog HDL 和 VHDL 的区别如下&#xff1a; 语法结构&#xff1a;Verilog的语法结构类似于C语言&#xff0c;而VHDL的语法结构则更接近…

【网络安全 | 信息收集 | 渗透工具】FofaViewer工具的安装使用详细教程+程序闪退问题解决

前言 安装教程 下载地址&#xff1a; Releases wgpsec/fofa_viewer GitHub 通过java --version查看JDK版本&#xff1a; (1)若使用的是高版本的JDK&#xff0c;则直接下载FofaViewer下载页面中 FofaViewer_1.1.13.zip的安装包。 (2)若使用的是JDK8&#xff0c;则下载FofaV…