多线程的学习上篇

previewfile_2862005448123

座右铭: 天行健,君子以自强不息;地势坤,君子以厚德载物.

引入进程这个概念的目的

引入进程这个概念,最主要的目的,是为了解决“并发编程"这样的问题.
这是因为CPU进入了多核心的时代
要想进一步提高程序的执行速度,就需要充分的利用CPU 的多核资源

其实,多进程编程,已经可以解决并发编程的问题了.已经可以利用起来CPU多核资源了.
但是它带来了新的问题 => 那就是进程太重了(消耗资源多&速度慢)!!!
创建一个进程,开销比较大.
销毁一个进程,开销也比较大.
调度一个进程,开销还比较大.

进程虽然能解决问题,但是它不是作为这个问题的最优解法
期望一种开销更小,重量更轻的这样的一个概念来解决这个“并发编程”
因此,线程应运而生!!!线程也叫做“轻量级进程”
它在解决“并发编程”问题的前提下,让创建,销毁,调度的数速度,更快了一些!!!

进程是操作系统中的基本单位.创建进程,就得给它分配一些资源(如内存资源和硬盘资源……)
在分配资源的时候,还得消耗一些时间进程重,主要就是重在"资源分配/回收"上.

为什么线程更轻量???

=> 它把申请资源/释放资源的操作给省下了.

以工厂的扩建为例:
方案一: 在另外的地方,再建一个跟原来工厂一模一样的分厂(工厂二号)
~~ 需要新的场地和新的物流体系,一套新的生产线….
=> 多进程版本的方案

image-20230918093634503

方案二: 在原来工厂上进行扩建,增加一套生产线 ~~ 成本比第一种方案小很多
~~ 场地和物流体系,都是可以复用之前的,只需要一套新的生产线……
~~ 多线程版本的方案 => 此时,只是在第一套生产线的时候,才需要把资源申请到位,
后续再加新的生产线,就复用之前的资源即可.

image-20230918094220221

线程和进程的关系

~~ 是进程包含线程
一个进程可以包含一个线程,也可以包含多个线程.(不能没有)
只有第一个线程启动的时候,开销是比较大的.后续线程就省事了.

同一个进程里的多个线程之间,共用了进程的同一份资源(主要指的是内存文件描述符表)

  • 内存 ~~ 线程1new的对象,在线程2,3,4里都可以直接使用
  • 文件描述表 ~~线程1打开的文件,在线程2,3,4里都可以直接使用

操作系统,实际调度的时候,是以线程为单位进行调度的.
(之前那篇博客讲进程调度,相当于每个进程里面只有一个线程这样的情况)

如果每个进程有多个线程了,每个线程是独立在CPU上调度的
=> 线程是操作系统调度执行的基本单位

一个线程可以在多个进程里面嘛?
不可以!!进程和线程是一个—对多的关系.

一个核心上执行的是一个线程.如果你一个进程有两个线程.
线程1可能在核心A上执行,线程2可能在核心B上执行.
操作系统调度的时候,其实不关心进程,而是只关心线程

一个线程也是通过一个PCB 来描述的
一个进程里面可能是对应一个PCB,也可能是对应多个PCB
之前介绍的,PCB里的状态,上下文,优先级,记账信息,都是每个线程有自己的.各自记录各自的.
但是同一个进程里的PCB之间,pid是一样的,内存指针和文件表述表也是一样的
=> 根据相同的pid来区分他们是同一个进程的.

谈到"调度”已经和进程没啥关系了.
进程专门负责资源分配.
线程来接管和调度相关的一切内容.

理论是为了指导我们编程的,不懂这个理论,
就不知道这个代码为什么这么写!!!

图示理解

最初的需求
1个滑稽老铁,吃100只鸡 ~~ 效率比较低
image-20230918154821678

如何吃得快点,有多进程多线程两个版本,如下图:
多进程吃鸡的方式:
~~ 成本比较高

image-20230918155101436

多线程吃鸡的方式:

image-20230918155347489提高这里滑稽老铁的个数,速度能否更快了???

image-20230918160353714

但是如果继续提高滑稽老铁的个数了???
image-20230918163513486

(1)在多线程的情况下,多个滑稽老铁,共享同一份鸡肉,此时就可能会打架

image-20230918170001294

(2)多线程还有一个情况 ~~

image-20230918163927661

注: chrome浏览器要使用多进程编程模型(每个标签页都是个进程)
~~ 目的就是为了防止,一个页面挂了把别的页面带走.

什么时候会存在安全问题???

多个执行流访问同一个共享资源的时候.
~~ 线程模型,天然就是资源共享的.多线程争抢同一个资源(同一个变量)非常容易触发的.
~~ 进程模型,天然是资源隔离的,不容易触发.
进行进程间通信的时候,多个进程访问同一个资源,可能会出问题.

多线程会提高效率,但是不如多进程安全.
但是代码写的好,线程安全问题,就不怕
=> 当前多线程的编程模型,要比多进程这种模型应用更加广泛.
工作中遇到多线程的情况也要比多进程多很多.尤其是Java圈子.

面试中,进程和线程的区别,就是"操作系统"这个模块最高频的问题,没有之一!!!




在Java中如何进行多线程编程

本身关于线程的操作,都是操作系统提供的API
Java是个跨平台的语言.很多操作系统的提供的功能,都被JVM给封装好了.
咱们不需要学习系统原生API,只需要学习Java提供的API就行了.


Java是如何实现跨平台的???
  • windows系统,实现了一个windows版本的JVM

  • Linux系统,实现了Linux版本的JVM

  • Mac系统,实现了Mac 版本的JVM

  • 随便某个系统, 也实现了对应版本的JVM

跨平台是靠无数个不同版本的JVM支持的!
这些不同的JVM内部封装了不同系统的API,对上都是执行同样规则的字节码
例子: 你去欧洲周游,到各个国家打卡
到了英国,就去请一个英国的翻译(既会说英语,也会说汉语),帮助我与本地人沟通;
到了法国,就又找一个法国的翻译(既会说法语,也会说汉语),帮助我与本地人沟通;
到了德国,就又找一个德国的翻译(既会说德语,也会说汉语),帮助我与本地人沟通;
………
Java跨平台,就于有几个平台,就有几个“翻译”…
~~ 点评: “大力出奇迹!!!”


Java操作多线程,最核心的类Thread

~~ 使用Thread类,不需要import别的包,因为它在java.lang下面的
类似的有: String, StringBuilder, StringBuffer

创建线程,是希望线程成为一个独立的执行流,它要能够执行一段代码.

第一个多线程程序

代码如下:

class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("hello thread");// 为了让这里的打印慢点, 方便看, 加个sleep, 休眠 1stry {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}
}public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}}
}

运行结果:

image-20230918212013737

操作系统调度线程的时候,“抢占式执行”,具体哪个线程先上,哪个线程后上,不确定,取决于操作系统调度器具体实现策略.
虽然有优先级,但是在应用程序层面上无法修改.
从应用程序(代码)的角度,看到的效果,就好像是线程之间的调度顺序是"随机"的一样.
=> 内核里本身并非是随机.但是干预因素太多,并且应用程序这一层也无法感知到细节,
就只能认为是随机的了!!!
执行顺序是由内核实现的,无解,但可以通过一些API进行有限度的干预.
线程安全问题的罪魁祸首,万恶之源,就是这里的抢占式执行,随机调度.

这里的Thread类,本质上还是系统里的线程的封装.
每个Thread的对象就对应到系统中的—个线程(也就对应一个PCB).

方法重写 Override: 父类有个方法,子类搞一个名字相同,参数也完全相同方法。
子类的方法就会被动态绑定的机制,被调用到.
Thead t = new MyThread();
t.run(); 虽然t是父类引用,此处调用的run仍然是子类的方法.(t本质上还是指向的子类的对象)

重载 Overload: 同一个作用域,多个方法,名字相同,参数个数或者类型不同,
同一个类里,或者是子类和父类之间…

t.sart(); => 线程中的特殊方法,启动一个线程.
start 里面没有调用run,而是创建了个线程.由新的线程来执行run方法.
start如何创建新的线程 => 就是调用操作系统的API,
通过操作系统内核创建新线程的 PCB(程序控制块),并且把要执行的指令交给这个PCB.
当 PCB 被调度到CPU上执行的时候,也就执行到了线程run方法中的代码了
如果run方法执行完毕,新的这个线程自然销毁.

new Thread对象这个操作不创建线程.(这里说的线程指的是系统内核里的PCB)
调用start才是创建的PCB,这才有货真价实的线程的.

start 和 run之间的区别:
start是真正创建了一个线程(从系统这里创建的),线程是独立的执行流.
run 只是描述了线程要干的活是啥.如果直接再main中调用run, 此时没有创建新线程,全是main线程一个人干活.

PCB对应的是线程.一个线程对应一个PCB一个进程对应多个PCB.
如果一个进程只有一个线程,就是一个进程对一个PCB了.
同一个进程里的若干PCB pid相同.不同进程的pid是不同的.
PCB不是"简称",是一个数据结构,体现的是进程/线程是如何实现,如何被描述出来的.
PCB只是操作系统书里说的概念.实际上Linux对应结构体名字叫做task_struct

如何直观的看到上述代码中的两个线程???
可以使用JDK自带的工具 jconsole 查看当前的Java进程中的所有线程.
image-20230918215407492

注: JDK => Java开发工具包,这里带的工具很多的,不只是javac和java.
image-20230918220407913

注:进程包含线程 => 要想看到线程,得先找到对应的进程,再看进程里有哪些线程.

点击一下上图中的thread.ThreadDemo1
image-20230918220859922

image-20230918221747327

如果你打开JConsole,在本地进程什么都看不到,尝试右键,以管理员方式运行.

image-20230918224445511



Java中创建线程的多种写法
  1. 继承Thread,重写run方法

    1.继承 Thread 来创建一个线程类
    2.创建 MyThread 类的实例
    3.调用 start 方法启动线程

    
    class MyThread extends Thread{@Overridepublic void run() {System.out.println("hello thread");}
    }
    public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();}
    }
    
  2. 实现Runnable 接口

    1.实现 Runnable 接口
    2.创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数
    3.调用 start 方法

    Runnable 作用,是描述一个"要执行的任务",run方法就是任务的执行细节

    class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("hello thread");}
    }
    public class ThreadDemo2 {public static void main(String[] args) {// 这只是描述了一个任务Runnable runnable = new MyRunnable();// 把任务交给线程来执行Thread t = new Thread(runnable);t.start();}
    }
    

    这样写的好处:
    解耦合.目的就是为了让线程和和线程要干的活之间分离开.
    未来如果要改代码,不用多线程,使用多进程,或者线程池,或者协程…此时代码改动比较小.

  3. 使用匿名内部类, 继承 Thread

    1.创建了一个Thread的子类.(子类没有名字)所以才叫做“匿名”
    2.创建了子类的实例,并且让t引用指向该实例

    public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {System.out.println("hello");}};t.start();}
    }
    
  4. 使用匿名内部类, 实现 Runnable

    这个写法和方法2本质相同.
    只不过是把实现Runnable任务交给匿名内部类的语法.
    此处是创建了一个类,实现 Runnable,同时创建了类的实例,并且传给Thread的构造方法.

    public class ThreadDemo4 {public static void main(String[] args) {Thread t =new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});t.start();}
    }
    
  5. 使用Lambda表达式 ~~ 最简单,推荐使用的写法

    把任务用lambda表达式来描述
    直接把 lambda传给Thread构造方法.
    lambda就是个匿名函数(没有名字的函数)用一次就销毁了.
    Java里面函数没法脱离类存在.java为了能和别的语言对齐, 搞了个蹩脚的函数式接口,通过这个来实现lambda .

    public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(()-> {System.out.println("hello");});t.start();}
    }
    

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

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

相关文章

《PostgreSQL中的JSON处理:技巧与应用》

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: &#x1f4da…

为什么qt设置了utf-8 bom 格式后还是有乱码

有乱码 void SingleApplication::_showInstanceRunningDialog() {// 创建一个提示窗口QMessageBox msgBox;msgBox.setIcon(QMessageBox::Information);msgBox.setWindowTitle("应用已运行");msgBox.setText("应用程序已经在运行中。");msgBox.setStandardB…

【深度学习实验】线性模型(二):使用NumPy实现线性模型:梯度下降法

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 初始化参数 2. 线性模型 linear_model 3. 损失函数loss_function 4. 梯度计算函数compute_gradients 5. 梯度下降函数gradient_descent 6. 调用函数 一、实验介绍 使用Nu…

RocketMQ 发送顺序消息

文章目录 顺序消息应用场景消息组(MessageGroup)顺序性生产的顺序性MQ 存储的顺序性消费的顺序性 rocketmq-client-java 示例(gRPC 协议)1. 创建 FIFO 主题生产者代码消费者代码解决办法解决后执行结果 rocketmq-client 示例&…

【结构型】代理模式(Proxy)

目录 代理模式(Proxy)适用场景代理模式实例代码(Java) 代理模式(Proxy) 为其他对象提供一种代理以控制对这个对象的访问。Proxy 模式适用于在需要比较通用和复杂的对象指针代替简单的指针的时候。 适用场景 远程代理 (Remote Proxy) 为一个对象在不同…

《ADS2011射频电路设计与仿真实例》功率放大器设计的输入输出匹配

徐兴福这本书的6.6 Smith圆图匹配这一节中具体匹配时,直接给出了电容与串联微带的值,没有给出推导过程,我一开始以为是省略了详细推导过程,后来发现好像基本上是可以随便自己设的。以输入匹配(书本6.6.4输入匹配电路的…

Modbus RTU(Remote Terminal Unit)与RS-485协议介绍(主站设备(Master)、从站设备(Slave))

文章目录 Modbus RTU与RS-485协议介绍一、引言二、Modbus RTU 协议介绍2.1 Modbus RTU 协议简介2.2 Modbus RTU 协议帧结构主站设备、从站设备与从站设备地址2.3 Modbus RTU 协议举例 三、RS-485 协议介绍3.1 RS-485 协议简介3.2 RS-485 物理连接方式3.3 RS-485 与 Modbus RTU …

LeetCode-热题100-笔记-day31

105. 从前序与中序遍历序列构造二叉树https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历&#xff0c…

全国职业技能大赛云计算--高职组赛题卷④(容器云)

全国职业技能大赛云计算--高职组赛题卷④(容器云) 第二场次题目:容器云平台部署与运维任务1 Docker CE及私有仓库安装任务(5分)任务2 基于容器的web应用系统部署任务(15分)任务3 基于容器的持续…

企业架构LNMP学习笔记61

Nginx作为tomcat的前段反向代理: 在实际业务环境中,用户是直接通过域名访问,基于协议一般是http、https等。默认tomcat运行在8080端口。一般会通过前端服务器反向代理到后端的tomcat的方式,来实现用户可以通过域名访问tomcat的we…

2023全新TwoNav开源网址导航系统源码 | 去授权版

2023全新TwoNav开源网址导航系统源码 已过授权 所有功能可用 测试环境:NginxPHP7.4MySQL5.6 一款开源的书签导航管理程序,界面简洁,安装简单,使用方便,基础功能免费。 TwoNav可帮助你将浏览器书签集中式管理&#…

Java面试八股文宝典:初识数据结构-数组的应用扩展之HashMap

前言 除了基本的数组,还有其他高级的数据结构,用于更复杂的数据存储和检索需求。其中,HashMap 是 Java 集合框架中的一部分,用于存储键值对(key-value pairs)。HashMap 允许我们通过键来快速查找和检索值&…

【数据结构】树的存储结构;树的遍历;哈夫曼树;并查集

欢~迎~光~临~^_^ 目录 1、树的存储结构 1.1双亲表示法 1.2孩子表示法 1.3孩子兄弟表示法 2、树与二叉树的转换 3、树和森林的遍历 3.1树的遍历 3.1.1先根遍历 3.1.2后根遍历 3.2森林的遍历 3.2.1先序遍历森林 3.2.2中序遍历森林 4、树与二叉树的应用 4.1哈夫曼树…

【Linux网络编程】Socket-TCP实例

该代码利用socket套接字建立Tcp连接,包含服务器和客户端。当服务器和客户端启动时需要把端口号或ip地址以命令行参数的形式传入。服务器启动如果接受到客户端发来的请求连接,accept函数会返回一个打开的socket文件描述符,区别于监听连接的lis…

【校招VIP】前端网络之路由选择协议

考点介绍 当两台非直接连接的计算机需要经过几个网络通信时,通常就需要路由器。路由器提供一种方法来开辟通过一个网状联结的路径。在图R-9中标示了几条存在于洛杉矶和纽约办公室的路径。这种网状网络提供了冗余路径以调整通信负载或倒行链路,通常有一条…

灰狼算法优化ICEEMDAN参数,四种适应度函数任意切换,最小包络熵、样本熵、信息熵、排列熵...

今天给大家带来一期由灰狼算法优化ICEEMDAN参数的MATLAB代码。 优化ICEEMDAN参数的思想可以参考该文献: [1]陈爱午,王红卫.基于HBA-ICEEMDAN和HWPE的行星齿轮箱故障诊断[J].机电工程,2023,40(08):1157-1166. 文献原文提到:由于 ICEEMDAN 方法的分解效果取…

【数据结构】队列知识点总结--定义;基本操作;队列的顺序实现;链式存储;双端队列;循环队列

欢迎各位看官^_^ 目录 1.队列的定义 2.队列的基本操作 2.1初始化队列 2.2判断队列是否为空 2.3判断队列是否已满 2.4入队 2.5出队 2.6完整代码 3.队列的顺序实现 4.队列的链式存储 5.双端队列 6.循环队列 1.队列的定义 队列(Queue)是一种先…

Vue3记录

Vue3快速上手 1.Vue3简介 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)耗时2年多、2600次提交、30个RFC、600次PR、99位贡献者github上的tags地址:https://github.com/vuejs/vue-next/releas…

软件需求怎么写?

前言:一般来说,软件产品的需求人员的主要输出物就是软件需求,如果这个软件产品就XX系统,人们口中的“系统需求”和“软件需求”就没有什么区别了。在车企行业,推行这ASPICE体系,在这个体系中明确申请了系统…

DMNet复现(一)之数据准备篇:Density map guided object detection in aerial image

一、生成密度图 密度图标签生成 采用以下代码,生成训练集密度图gt: import cv2 import glob import h5py import scipy import pickle import numpy as np from PIL import Image from itertools import islice from tqdm import tqdm from matplotli…