多线程(JavaEE初阶系列2)
目录
前言:
1.什么是线程
2.为什么要有线程
3.进程与线程的区别与联系
4.Java的线程和操作系统线程的关系
5.多线程编程示例
6.创建线程
6.1继承Thread类
6.2实现Runnable接口
6.3继承Thread,使用匿名内部类
6.4实现Runnable接口,使用匿名内部类
6.5lambda表达式创建Runnable子类对象
7.Thread类及常见的方法
7.1Thread中常见的构造方法
7.2Thread的几个常见属性
结束语:
前言:
这节中小编就来和大家聊一聊多线程是什么以及需要我们掌握多线程程序的编写、多线程的状态、什么是线程不安全及解决思路以及掌握synchronized、volatile关键字。在上节博客中给大家讲到了进程和进程管理,我们讲解了为什么要使用调度,CPU的按照并发的方式来执行进程的,在PCB中也提供了一些属性,里面有进程的优先级、进程的状态、进程的上下文、进程的记账信息......也给大家讲解了引入进程的目的就是为了能够实现多个任务并发执行的效果。接下来小编就给大家讲解一下什么是线程,线程与进程之间又有什么联系。
1.什么是线程
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。
2.为什么要有线程
首先“并发编程”称为“刚需”。
- 单核CPU的发展遇到了瓶颈,要想提高算力,就需要多核CPU,而并发编程能够更充分利用多核CPU资源。
- 有些任务场景需要“等待IO”,为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。
其次,虽然多进程也能实现并发编程,但是是线程比进程更轻量。
- 创建线程比创建进程更快。
- 销毁线程比销毁进程更快。
- 调度线程比调度进程更快。
最后,线程虽然比进程轻量,但是人们还不满足,于是又有了“线程池”和“协程”。
- 有关于线程池的话题我们后面在介绍,关于协程的话题这里我们不做过多的讨论。
3.进程与线程的区别与联系
进程有一个重大的问题就是比较重量,如频繁的创建/销毁进程,成本会比较高。
所以我们又引出了线程的概念。那么线程与进程之间到底有什么联系和区别呢?我们接着往下看。
进程是包含线程的。每一个进程至少有一个线程存在,即主线程。一个进程里可以有一个线程也可以有多个线程,每个线程之间都是一个独立存在的执行流,多个线程之间也是并发执行的。这里注意多个线程可能是在多个CPU核心上同时运行,也可能是在一个CPU核心上,通过快速调度,并发运行。操作系统真正的调度是在调度线程而不是在调度进程。
注意:线程是操作系统调度运行的基本单位!!!进程是操作系统资源分配的基本单位!!!
一个进程中的多个线程之间,共用一份系统资源。
- 内存空间。
- 文件描述符表。
注意:只有在进程启动的时候,创建第一个线程的时候需要花成本去申请系统资源。一旦进程(第一个线程)创建完毕,此时后续再创建线程就不必在申请资源了,这样创建和销毁的效率就会大大提高。
下面小编给大家举个例子方便大家更好的理解进程与线程。
所以通过上述的例子再给大家总结一下进程与线程之间的区别:
- 进程包含线程。
- 进程有自己独立的内存空间和文件描述符表,同一个进程中的多个线程之间共享一份地址空间和文件描述符表。
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位。
- 进程之间具有独立性,一个进程挂了不会影响其他进程;同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响其他进程。
4.Java的线程和操作系统线程的关系
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用。
Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。
5.多线程编程示例
上面说了这么多的概念,接下来还是带着大家一起在代码中切实的感受一下究竟什么是多线程。
代码展示:
package Thread;
class MyThread extends Thread{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//start会创建新的线程。t.start();//run不会创建新的线程。run是在main线程中执行的。//t.run();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
代码分析:
在上述代码中我们看到我们写了一个类MyThread并继承了Thread类,在java中标准库提供了一个类Thread,它能够表示一个线程。
在继承Thread的时候我们需要对父类中的run()方法进行重写。
- 在主函数中我们先创建出来了一个MyThread的实例。
Thread t = new MyThread();
- 紧接着我们就要启动线程。
t.strat();
在上述代码中我们涉及到了两个线程。
- main方法所对应的线程(一个进程里面至少有一个线程)也可以称为主线程。
- 通过t.strat创建一个新的线程。
结果分析:
我们执行代码之后看到的效果是“hello t”和“hello main”是交替打印的,但又不是严格意义上的交替打印。按照我之前的单线程的想法,如果我们执行main线程中的进入while循环之后由于是死循环,那么按照之前的想法应该是出不来的,应该是要一值打印“hello main”的,但是这里并不是我们想象中的那样,而是和另一个线程中的一起交替打印,那就说明这里是启动了两个线程,两个线程分别独立运行。多线程在CPU上的调度是不确定的是随机的。所以我们看到的就是不规律的交替打印。这也就是我们上述中提到的一个进程中的多个线程并发执行的过程。
同时我们可以借助jak里面的工具jconsole来分析java里的线程。
在下面的路径下找到安装路径,然后双击打开运行。
点击线程,在下面可以看到很多线程这里你会发现有很多线程,这里不只有咱们创建出来的两个。除了标记出来的两个线程之外其他都是JVM自己创建出来的。
描述出了当前这两线程执行到哪里了。
6.创建线程
6.1继承Thread类
我们可以使用Thread,重写run方式来创建线程。
代码展示:
package Thread;
//使用继承Thread,重写run方法来创建线程
class MyThread extends Thread{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();//start会创建新的线程。t.start();//run不会创建新的线程。run是在main线程中执行的。//t.run();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.2实现Runnable接口
使用实现Runable,重写run。
代码展示:
package Thread;
//使用Runnable接口来实现线程创建
//1.实现Runnable接口
class MyRunnable implements Runnable{@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo2 {public static void main(String[] args) {//2.创建爱你Thread类实例,调用Thread的构造方法是将Runnable对象作为target参数。MyRunnable runnable = new MyRunnable();Thread t = new Thread(runnable);//3.调用start方法。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()
6.3继承Thread,使用匿名内部类
代码展示:
package Thread;
//继承Thread,使用匿名内部类来创建线程
public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.4实现Runnable接口,使用匿名内部类
代码展示:
package Thread;public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
6.5lambda表达式创建Runnable子类对象
这个也是创建线程最推荐的写法,使用lambda表达式也是最直观的简单的写法!!!
前面也给大家讲解过有关于lambda表达式的用法,如果还有不会的同学请点击下面的链接先去看看lambda表达式的使用吧!!!(http://t.csdn.cn/zEPFB)
代码展示:
package Thread;
//使用lambda表达式来创建线程
public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//启动线程t.start();//主线程while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
7.Thread类及常见的方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都由一个唯一的Thread对象与之关联。Java代码中的Thread对象和操作系统中的线程是一一对应的。
用我们上述的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
7.1Thread中常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
代码演示案例:
package Thread;public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"我的线程");t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果展示:
我们可以看到在上述运行的线程中就会出现自己命名的线程名字。
7.2Thread的几个常见属性
属性 | 获取 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
代码展示:
package Thread;public class ThreadDemo7 {public static void main(String[] args) {Thread t = new Thread(() -> {
// System.out.println("hello t");},"我的线程");t.start();System.out.println("线程ID是:" + t.getId());System.out.println("线程名称是:" + t.getName());System.out.println("线程状态是:" + t.getState());System.out.println("线程优先级是:" + t.getPriority());System.out.println("线程是否是后台线程:" + t.isDaemon());System.out.println("线程是否存活:" + t.isAlive());System.out.println("线程是否被中断:" + t.isInterrupted());}
}
结果展示:
解释getDaemon:
- true表示是后台线程。后台线程不会阻止java进程结束,哪怕后台线程还没有执行完,java进程该结束就会结束。
- false表示是前台线程。前台线程会阻止java进程结束,必须得java进程中的所有前台线程都执行完java进程才会结束。
注意:创建的线程默认是前台的,也可以通过setDaemon修改成后台线程。
解释isAlive:
是描述系统内核里的那个线程是否还存活。线程的入口方法执行完毕,此时系统中的对应的线程就没有了,此时调用该线程isAlive就是false。
结束语:
这节中小编主要是给大家分享了线程了概念、线程与进程之间的区别和联系以及如何创建线程。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)
相关文章:

多线程(JavaEE初阶系列2)
目录 前言: 1.什么是线程 2.为什么要有线程 3.进程与线程的区别与联系 4.Java的线程和操作系统线程的关系 5.多线程编程示例 6.创建线程 6.1继承Thread类 6.2实现Runnable接口 6.3继承Thread,使用匿名内部类 6.4实现Runnable接口,使…...
Ubuntu20.04点Ubuntu software没反应,打不开的解决方案(Ubuntu笔记)
首先检查Ubuntu Software的状态,在终端输入:systemctl status snap.ubuntu-software.ubuntu-software.service 如果状态显示为inactive,则需要启动snap.ubuntu-software.ubuntu-software.service,在终端输入:sudo sys…...

力扣1114.按序打印-----题目解析
题目描述 解析: class Foo {public int a 0;public Foo() {}public void first(Runnable printFirst) throws InterruptedException {// printFirst.run() outputs "first". Do not change or remove this line.printFirst.run();a;}public void second…...
Centos7.9安全部署_防火墙配置_端口配置_协议配置_IP配置_全部亲测---记录022_大数据工作笔记0182
在我们平时搭建大数据平台的时候,由于防火墙的限制,会让搭建集群的时候,报各种错误,但是,有些网络环境要求比较严格的地方,防火墙又要求必须要放开,尤其是.. 有些网络环境会安全组进行定时扫描,说实话,我们用的很多开源软件,一般都是低版本的话都有漏洞,但是升级的话又会很容易…...

Tik Tok海外公会是什么?
在数字社交媒体领域,TikTok已成为全球性的热门应用之一,印尼市场也不例外。作为全球第四人口最多的国家,印尼的年轻人口众多,是一个极具潜力的市场。对于希望在印尼市场进行TikTok公会申请的机构来说,了解市场发展趋势…...
elasticsearch批量导入问题
Testvoid bulkIndex() throws IOException{List<Product> products new ArrayList<Product>();products.add(new Product("香烟",135,1)); // products.add(new Product("瓜子",154,2)); // products.add(new Product("矿…...

SAP安装笔记
1、准备安装介质,SWPM10SP25,51050829_NW750_JavaExport、SAP_HANA_CLIENT、kernel放到/sapcd/NetWeaver目录下 进入SWPM10SP25执行./sapinst安装 2、待出现 “Open your browser and paste the following URL address to access the G…...

速速收藏!程序员副业赚钱的8种模式
程序员副业赚钱的方式有很多,我总结了程序员兼职赚钱,增加收入的八种模式,希望能够给在迷茫的程序员一点参考! 投资理财 很多程序员多少都会买买基金、炒炒股作为一个小副业,由于程序员大多是理科生,有比…...

2、基于redis实现分布式锁
目录 2.1. 基本实现2.2. 防死锁2.3. 防误删2.4. redis中的lua脚本2.4.1 redis 并不能保证2.4.2 lua介绍 2.5. 使用lua保证删除原子性 2.1. 基本实现 借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setn…...

【问题记录】Ubuntu 22.04 环境下,程序报:段错误(核心已转储)怎么使用 core 文件和GDB调试器 解决?
目录 环境 问题情况 解决思路 原因分析 解决方法 番外知识 环境 VMware Workstation 16 Pro (版本:16.1.2 build-17966106)ubuntu-22.04.2-desktop-amd64 问题情况 本人在运行百万并发的服务端程序时,程序运行报:…...
9 Linux实操篇-实用指令
9 Linux实操篇-实用指令 文章目录 9 Linux实操篇-实用指令9.1 指定和修改运行级别-init/systemctl9.2 找回root密码9.3 Linux的指令说明9.3 帮助类-man/help9.4 文件目录类-pwd/ls/cd/mkdir/...9.5 时间日期类-date/cal9.6 搜索查找类-find/locate/which/grep9.7 压缩和解压类-…...

Hbase基础概念
HBase 一、HBase的数据模型1.HBase数据存储结构2.HBase存储概念3.HBase基本架构 二、HBase Shell1.DDL(Data Definition Language)1.namespace2.table 2.DML(Data Manipulation Language)1.写入数据2.读取数据3.删除数据 三、HBase组成架构1. Master架构…...
JTS-Angle角度类
目录: 获取AB连线与正北方向的角度求距离此点一定距离、一定夹角的点经纬度判断point点 在线段startPoint-etartPoint的左侧或者右侧米转换为弧度弧度转换为米 定义Point点 public class LatLngPoint {final static double RC 6378137;final static double RJ …...

pytest---环境切换(base-url)
前言 前面小编介绍了如何通过pytest的插件来实现自动化测试的环境的切换,当时使用的方法是通过钩子函数进行获取命令行参数值,然后通过提前配置好的参数进行切换测试环境地址,今天小编再次介绍一种方法,通过pytest的插件ÿ…...

linux跑代码,程序终止了,但资源没有释放。
linux跑代码,程序终止了,但资源没有释放。 程序终止,但是资源没有释放. kill -9 5062完成。 linux终止进程...

数据结构--线性表2-1
目录 一、线性结构的定义 二、线性表的表示 三、顺序表的实现(或操作) 1、修改: 2、插入: 四、顺序表的运算效率分析:时间效率分析: 一、线性结构的定义 若结构时非空有限集,则有且仅有一个…...

网访问内网机器:基于frp的内网穿透
随缘更新些我自己的博客网站里的文章吧 因为经常需要远程访问自己的机器,所以写一个博客记录一下 公网访问内网机器:基于frp的内网穿透 从公网中访问自己的私有设备向来是一件难事儿。 1. 为什么需要内网穿透? A. 计算机网络 如何在自己的机…...

【Spring框架】Spring读取与存储综合练习
练习 在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返…...

Python实现指定区域桌面变化监控并报警
在这篇博客中,我们将使用Python编程语言和一些常用的库来实现一个简单的区域监控和变化报警系统。我们将使用Tkinter库创建一个图形界面,允许用户选择监控区域,并使用OpenCV库进行图像处理和相似性比较,以检测区域内的变化&#x…...

【数据结构】实验五:栈
实验五 栈 一、实验目的与要求 1)熟悉栈的类型定义和基本操作; 2)灵活应用栈解决具体应用问题。 二、实验内容 1、判断回文数,回文是指正读反读均相同的字符序列,如“1221”和“12321”均是回文,但“…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...