「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{@Overridepublic 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{@Overridepublic 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,然后在内核中完成这些操作

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

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

相关文章

02_对象树

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

若依从0到1部署

服务器安装 MySQL8 Ubuntu 在 20.04 版本中&#xff0c;源仓库中 MySQL 的默认版本已经更新到 8.0&#xff0c;因此可以直接使用 apt-get 安装。 设置 apt 国内代理 打开 https://developer.aliyun.com/mirror/ 阿里云镜像站&#xff0c;找到适合自己的系统&#xff1a; 找…

轻松查询车辆信息的全能接口

在当今社会&#xff0c;车辆已经成为人们出行的重要工具之一。当我们在二手车买卖、事故处理或者其他需要查询车辆详细信息的情况下&#xff0c;我们通常需要耗费大量时间和精力去收集相关的资料。幸好&#xff0c;有了车辆信息查询接口&#xff0c;我们可以通过输入车架号vin来…

SSH协议的优缺点

SSH&#xff08;Secure Shell&#xff09;是一种用于在计算机网络上进行安全远程访问和执行命令的协议。提供加密通信通道&#xff0c;防止敏感信息在传输过程中被窃听或篡改。SSH还支持文件传输和端口转发等功能&#xff0c;使其成为广泛使用的安全远程管理工具。 1. 安全远程…

【设计模式】聊聊观察者设计模式原理及应用

原理 观察者模式属于行为模式&#xff0c;行为模式主要解决类和对象之间交互问题。 含义&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变时&#xff0c;所有依赖的对象会自动通知。 被依赖的对象被观察者(Observable) &#xff0c;依赖的对象观察…

Go: 理解 Sync.Pool 的设计

sync 包提供了一个强大且可复用的实例池&#xff0c;以减少 GC 压力。在使用该包之前&#xff0c;我们需要在使用池之前和之后对应用程序进行基准测试。这非常重要&#xff0c;因为如果不了解它内部的工作原理&#xff0c;可能会影响性能。 池的限制 我们来看一个例子以了解它…

【电控笔记3.5】三相逆变器

基础 大小调变指标ma 频率调变指标mf 载波频率:pwm频率

日本极致产品力 | 井村屋红豆棒冰如何年销2.5亿根

《极致产品力》日本深度研学是一个顾问式课程,可以帮助企业找产品、找方向、找方法,在日本终端市场考察中洞悉热销产品背后的成功逻辑&#xff0c;了解最新最前沿的产品趋势和机会。结合日本消费趋势中国转化的众多经验,从品牌、包装、卖点、技术和生产艺等多方面寻找中国市场的…

MoCo v2 论文解读

paper&#xff1a;Improved Baselines with Momentum Contrastive Learning official implementation&#xff1a;https://github.com/facebookresearch/moco 这篇文章的内容只有2页&#xff0c;不能称之为paper&#xff0c;作者本人也称之为note。主要内容就是将SimCLR中的两…

1.5MHz,1.2A COT 架构同步降压变换器只要0.16元,型号:LN3435

推荐原因 1.5MHZ的开关频率&#xff0c;可以使用小电感&#xff0c;1.2A满足多数应用&#xff0c;价格感人&#xff0c;只要0.16元 产品概述 LN3435是一款电流模COT架构同步降压开关稳压器。 输入范围为 2.7V-6.0V&#xff0c;可提供 1.2A 的连续输出电流。 内部集成了低内阻…

学习Rust的第4天:常见编程概念

基于Steve Klabnik的《The Rust Programming Language》一书。昨天我们做了一个猜谜游戏 &#xff0c;今天我们将探讨常见的编程概念&#xff0c;例如&#xff1a; Variables 变量Constants 常数Shadowing 阴影Data Types 数据类型Functions 功能 Variables 变量 In layman ter…

C语言入门第四天(数组)

一、C语言数组的基本语法 1.数组的定义 数组是 C 语言中的一种数据结构&#xff0c;用于存储一组具有相同数据类型的数据。数组中的每个元素可以通过一个索引&#xff08;下标&#xff09;来访问&#xff0c;索引从 0 开始&#xff0c;最大值为数组长度减 1。 2.定义语法格式 …

4个步骤:如何使用 SwiftSoup 和爬虫代理获取网站视频

摘要/导言 在本文中&#xff0c;我们将探讨如何使用 SwiftSoup 库和爬虫代理技术来获取网站上的视频资源。我们将介绍一种简洁、可靠的方法&#xff0c;以及实现这一目标所需的步骤。 背景/引言 随着互联网的迅速发展&#xff0c;爬虫技术在今天的数字世界中扮演着越来越重要…

Python也可以合并和拆分PDF,批量高效!

PDF是最方便的文档格式&#xff0c;可以在任何设备原样且无损的打开&#xff0c;但因为PDF不可编辑&#xff0c;所以很难去拆分合并。 知乎上也有人问&#xff0c;如何对PDF进行合并和拆分&#xff1f; 看很多回答推荐了各种PDF编辑器或者网站&#xff0c;确实方法比较多。 …

支持向量机模型pytorch

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个支持向量机模型pytorch程序,最后打印5个条件分别的影响力。 示例一 支持向量机&#xff08;SVM&#xff09;是一种…

【原创】springboot+mysql理发会员管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

算法课程笔记——常用库函数

memset初始化 设置成0是可以每个设置为0 而1时会特别大 -1的补码是11111111 要先排序 unique得到的是地址 地址减去得到下标 结果会放到后面 如果这样非相邻 会出错 要先用sort排序 O&#xff08;n&#xff09;被O&#xff08;nlogn&#xff09;覆盖

服务器数据恢复—xfs文件系统节点、目录项丢失的数据恢复案例

服务器数据恢复环境&#xff1a; EMC某型号存储&#xff0c;该存储内有一组由12块磁盘组建的raid5阵列&#xff0c;划分了两个lun。 服务器故障&#xff1a; 管理员为服务器重装操作系统后&#xff0c;发现服务器的磁盘分区发生改变&#xff0c;原来的sdc3分区丢失。由于该分区…

葡萄书--深度学习基础

卷积神经网络 卷积神经网络具有的特性&#xff1a; 平移不变性&#xff08;translation invariance&#xff09;&#xff1a;不管检测对象出现在图像中的哪个位置&#xff0c;神经网络的前面几层应该对相同的图像区域具有相似的反应&#xff0c;即为“平移不变性”。图像的平移…

web自动化系列-selenium 的鼠标操作(十)

对于鼠标操作 &#xff0c;我们可以通过click()方法进行点击操作 &#xff0c;但是有些特殊场景下的操作 &#xff0c;click()是无法完成的 &#xff0c;比如 &#xff1a;我想进行鼠标悬停 、想进行鼠标拖拽 &#xff0c;怎么办 &#xff1f; 这个时候你用click()是无法完成的…