当前位置: 首页 > news >正文

【JavaEE初阶】认识线程、创建线程

1. 认识线程(Thread)

1.1 概念

1) 线程是什么

一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.

举例:

还是回到我们之前的银⾏的例⼦中。之前我们主要描述的是个⼈业务,即⼀个⼈完全处理⾃⼰的业务。我们进⼀步设想如下场景:
⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。
如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。 此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

2) 为何要有线程  

首先,我们来说一下进程。在多任务操作系统中,希望系统能够同时运行多个程序,这就引入了进程。如果是单任务的操作系统,就完全不涉及进程,也不需要管理(进程),更不需要调度。本质上说,进程是解决”并发编程“问题的,事实上,进程也可以很好地解决并发编程这样的问题。

但是在一些特定的环境下,进程的表现不尽人意,比如,有些场景下,需要频繁的创建和销毁进程,举例,最早的web开发,是使用C语言来编写的服务器程序(基于一种CGI这样的技术,其基于多进程的编程模式),服务器同一时刻会收到很多请求,针对每个请求,都会创建出一个进程,给这个请求提供一定的服务,返回对应的响应;一旦这个请求处理完了,此时这个进程就要销毁了。如果请求很多,就意味着服务器要不停地创建进程、销毁进程,此时使用多进程编程,系统的开销就会很大(开销主要体现在资源的申请和释放上)。 

⾸先, "并发编程" 成为 "刚需"

  • 单核 CPU 的发展遇到了瓶颈. 要想提⾼算力, 就需要多核 CPU,而并发编程能更充分利⽤多核 CPU资源
  • 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
  • 创建线程⽐创建进程更快.
  • 销毁线程⽐销毁进程更快.
  • 调度线程⽐调度进程更快.

多线程并发编程,效率更高,尤其是对于java进程,是要启动java虚拟机的,启动java虚拟机开销是更大的,搞多个java进程,就要多个java虚拟机。所以,java中不太去鼓励多进程编程。

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"
(Coroutine)

3) 进程和线程的区别

  • 进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
⽐如之前的多进程例⼦中,每个客户来银⾏办理各⾃的业务,但他们之间的票据肯定是不想让别⼈知道的,否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中,张三、李四、王五虽然是不同的执⾏流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最⼤区别。
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
  • ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).

另外注意:

  1. 同一个进程中的线程之间,可能会互相干扰,引起线程安全问题
  2. 线程也不是越多越好,要能够合适,如果线程太多了,调度开销可能会非常明显 

 

4) Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念, 操作系统内核 实现了线程这样的机制, 并且对用户层提供了⼀些 API 供用户使用(例如 Linux 的 pthread 库)。
对于操作系统内核:
操作系统内核,是操作系统最核心部分的功能模块(管理硬件、给软件提供稳定的运行环境)。
操作系统 = 内核 + 配套的应用程序
这里用银行为例来说明一下:
当你到银行进行各种业务的办理的时候,都是需要在办事窗口前,给工作人员说清楚你的需求,由工作人员代办。我们知道银行中的办事窗口内部和银行大厅是分隔开的,你是进不去办事窗口内部的, 这里的办事窗口内部就相当于操作系统内核空间(内核态),你所在大厅则是用户空间(用户态)。 

为什么划分出用户态、内核态:

 最主要的目的,还是为了“稳定”。防止你的应用程序,把硬件设备或软件资源给搞坏了。系统封装了一些api,这些api都属于是一些“合法”的操作,应用程序只能调用这些api,这样就不至于对系统/硬件设备产生太大的危害。

假设让应用程序直接操作硬件,可能极端情况下,代码出现bug,就把硬件干烧了。

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进⼀步的抽象和封装。

 1.2 第⼀个多线程程序

感受多线程程序和普通程序的区别:
  • 每个线程都是⼀个独立的执行流
  • 多个线程之间是 "并发" 执行的
class MyThread2 extends Thread {//Thread类不用导包,属于特殊的包java.long,该包默认自动导入@Overridepublic void run() {//run方法就是该线程的入口方法while (true) {System.out.println("hello run");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo2 {public static void main(String[] args) {//2、根据刚才的类,创建出具体的实例(线程实例,才是真正的线程)Thread t = new MyThread2();//3、调用Thread的start方法,才会真正调用系统api,在系统内核中创建出线程t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}

对于上述代码,运行结果为两个循环不停地同时输出(验证了多个线程之间是 "并发" 执行的)。

我们知道,若对于普通程序来说,当遇到一个无限循环,会停留在这个循环,不停的打印输出,后续的代码是执行不到的。然而这个多线程程序,两个循环都执行到了,是因为每个线程都是⼀个独立的执行流 。代码中 t.start() ,即调用start()之后会创建一个新的线程,该线程进入到 run 方法,进行循环;而此时main线程,这个主线程会继续自己的执行,执行后续代码,也进行循环。

这里可以使用 jconsole 命令观察线程

2. 创建线程的几种方法

  •  方法1 继承 Thread 类

我们上面写的第一个多线程程序就是用的该方法。

1、继承 Thread 来创建⼀个线程类:
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}
2、创建 MyThread 类的实例:
MyThread t = new MyThread();
3、调⽤ start 方法启动线程:
 t.start();  //调用start才会真正地创建线程
  • 方法2 实现 Runnable 接口

1、实现 Runnable 接口

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

 2、创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

Thread t = new Thread(new MyRunnable());//或者另一种写法
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);

 3、调用start方法

t.start(); // 线程开始运⾏

该方法完整代码示例:

class MyThread3 implements Runnable {@Overridepublic void run() {while (true) {System.out.println("hello runnable");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo3 {public static void main(String[] args) {
//        Runnable runnable = new MyThread3();
//        Thread t = new Thread(runnable);Thread t = new Thread(new MyThread3());t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
对比上面两种方法:
  • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
  • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用,需要使用Thread.currentThread()来表示当前线程对象

其他创建方法

  • 匿名内部类创建 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 表达式创建 Runnable 子类对象 

// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});

3. 多线程的优势-增加运行速度

可以观察多线程在⼀些场合下是可以提高程序的整体运行效率的。
  • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串行的完成⼀系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {// 多线程并不⼀定就能提⾼速度,可以观察,count 不同,实际的运⾏效果也是不同的private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使⽤并发⽅式concurrency();// 使⽤串⾏⽅式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利⽤⼀个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运⾏结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串⾏: %f 毫秒%n", ms);}
}
并发: 399.651856 毫秒
串行: 720.616911 毫秒

 

该篇是对多线程的初步认识,接下来我会继续更新多线程的相关内容,请多多关注!

相关文章:

【JavaEE初阶】认识线程、创建线程

1. 认识线程&#xff08;Thread&#xff09; 1.1 概念 1) 线程是什么 一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码. 举例&#xff1a; 还是回到我们之前的银⾏的例⼦中。之前我们主要描…...

uniapp中uni.navigateBack返回后刷新页面数据

文章目录 一、前言1.1、[uni.navigateBack](https://uniapp.dcloud.net.cn/api/router.html#navigateback) 二、方法2.1、父页面设置钩子函数onBackPress2.2、uni.$emit和uni.$on监听通知数据变更2.2.1、子页面2.2.2、父页面 2.3、onShow钩子函数处理数据2.3.1、子页面2.3.2、父…...

sed文本 免交互

目录 什么是sed 概念 格式 基本用法 命令的选项 打印第三行 打印日志文件 打印奇数行 打印偶数行 第三行退出 删除第三行 sed在不打开文件的情况下修改文件内容 在后面添加 选项a 在字符中间添加 \n 实现追加换行 全部追加 在前面插入 选项i 替换 选项c …...

轻巧高效的剃须好工具,DOCO黑刃电动剃须刀上手

剃须刀大家都用过&#xff0c;我比较喜欢电动剃须刀&#xff0c;尤其是多刀头的悬浮剃须刀&#xff0c;感觉用起来很方便&#xff0c;剃须效率也很高。最近我在用一款DOCO小蔻的黑刃电动剃须刀&#xff0c;这款剃须刀轻巧易用&#xff0c;而且性价比超高。 相比于同类产品&…...

第15关 K8s HPA:自动水平伸缩Pod,实现弹性扩展和资源优化

------> 课程视频同步分享在今日头条和B站 大家好&#xff0c;我是博哥爱运维&#xff0c;这节课带来k8s的HPA 自动水平伸缩pod&#xff08; 视频后面有彩蛋 : ) &#xff09;。 我们知道&#xff0c;初始Pod的数量是可以设置的&#xff0c;同时业务也分流量高峰和低峰&a…...

接口测试工具(Jmeter)必学技巧

安装 使用JMeter的前提需要安装JDK&#xff0c;需要JDK1.7以上版本 目前在用的是JMeter5.2版本&#xff0c;大家可自行下载解压使用 运行 进入解压路径如E: \apache-jmeter-5.2\bin&#xff0c;双击jmeter.bat启动运行 启动后默认为英文版本&#xff0c;可通过Options – Choos…...

C++面试,说明const和#define的特点和区别

#define只是用来做文本替换的&#xff0c;例如&#xff1a; #define PI 3.1415926 float angel; angel 30 * PI /180; 当程序进行编译的时候。编译器会首先将"#define PI 3.1415926"以后所有代码中的PI都替换成3.1415926&#xff0c;然后进行编译。它的生命周期止…...

aikit 2023 3D与机械臂结合!

引言 今天我们主要了解3D摄像头是如何跟机械臂应用相结合的。我们最近准备推出一款新的机械臂套装AI Kit 2023 3D&#xff0c;熟悉我们的老用户应该知道&#xff0c;我们之前的AI Kit 2023套装使用的是2D摄像头。 随着技术进步&#xff0c;市场需求和领域的扩大&#xff0c;2D的…...

模拟退火算法应用——求解TSP问题

仅作自己学习使用 一、问题 旅行商问题(TSP) 是要求从一个城市出发&#xff0c;依次访问研究区所有的城市&#xff0c;并且只访问一次不能走回头路&#xff0c;最后回到起点&#xff0c;求一个使得总的周游路径最短的城市访问顺序。 采用模拟退火算法求解TSP问题&#x…...

【LeetCode】每日一题 2023_11_28 设计前中后队列(数组/链表/双端队列)

文章目录 刷题前唠嗑题目&#xff1a;设计前中后队列题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 这道题的难度&#xff0c;才是我想象中的中等题的难度好吧&#xff0c;昨天那玩意对我来说还是太难了…...

python基于YOLOv8全系列模型【n/s/m/l/x】开发构建不同参数量级的钢铁产业产品智能自动化检测识别系统

在前文的项目开发实践中&#xff0c;我们已经以钢铁产业产品缺陷检测数据场景为基准&#xff0c;陆续开发构建了多款目标检测模型&#xff0c;感兴趣的话可以自行阅读即可。 《YOLOv3老矣尚能战否&#xff1f;基于YOLOv3开发构建建钢铁产业产品智能自动化检测识别系统&#xf…...

力扣142. 环形链表 II

文章目录 力扣142. 环形链表 II示例代码实现总结收获 力扣142. 环形链表 II 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c…...

【设计模式-2.2】创建型——简单工厂和工厂模式

说明&#xff1a;本文介绍设计模式中&#xff0c;创建型设计模式中的工厂模式&#xff1b; 飞机大战 创建型设计模式&#xff0c;关注于对象的创建&#xff0c;本文介绍的简单工厂和工厂模式同样也是。举一个游戏例子&#xff0c;如飞机大战游戏中&#xff0c;屏幕中敌人类型…...

将文件读入C中的字符数组

当您使用 C 编程语言时&#xff0c;您可能会遇到一些需要将文件读入字符数组的问题&#xff0c;例如分析每个字符的频率&#xff0c;或者将所有句子的每个起始词从小写转换为大写&#xff0c;反之亦然。该解决方案非常简单&#xff0c;但对于不太了解文件读取或写入的人来说可能…...

不小心删除了短信,如何在 Android 上恢复已删除的短信

不小心删除了文字消息在 Android 手机上使用可能会是一种令人痛苦的体验。这些消息可能包含有价值的信息、珍贵的回忆或重要的细节。幸运的是&#xff0c;您可以探索多种方法来恢复这些丢失的消息。在本文中&#xff0c;我们将深入研究可用于检索已删除短信的选项&#xff0c;并…...

Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…...

springBoot的实现原理;SpringBoot是什么;使用SpringBoot的核心功能;springBoot核心注解以及核心配置文件

文章目录 springBootspringBoot的实现原理什么是 Spring Boot&#xff1f;SpringBoot是什么为什么要使用springBootSpring Boot的核心功能Spring Boot 主要有如下优点&#xff1a; SpringBoot启动过程-流程Spring Boot 的核心注解是哪个&#xff1f;什么是 JavaConfig&#xff…...

logback-spring.xml详解

《springboot使用logback日志框架超详细教程》文中&#xff0c;filter中最重要的两个过滤器LevelFilter&#xff08;日志级别精确匹配&#xff09;、ThresholdFilter&#xff08;阈值过滤&#xff09; 的描述非常准确&#xff1a; springboot使用logback日志框架超详细教程_sp…...

【Python】nn.BCEWithLogitsLoss函数详解

nn.BCEWithLogitsLoss() 是 PyTorch 中一个用于二元分类问题的损失函数&#xff0c;它结合了 Sigmoid 层&#xff08;将输出映射到 [0,1] 范围内&#xff09;和 Binary Cross Entropy&#xff08;BCE&#xff09;损失。这可以避免在正向和反向传播过程中可能出现梯度爆炸或梯度…...

【C++】日期类的实现

在上篇博客中我们已经学习了C中的运算符重载&#xff0c;我们说&#xff0c;操作符只能对于内置类型进行操作&#xff0c;对自定义类型我们需要自己定义函数去实现一系列的操作 那么这篇博客我们就专门把日期这个类单独拿出来写一下它都有哪些有意义的可以重载的运算符&#xf…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...