【JavaEE初阶】认识线程(Thread)
目录
🌾 前言
🌾 了解线程
🌈1.1 线程是什么?
🌈1.2 一些基本问题
🌾2、创建线程的方式
🌈 2.1 继承Thread类
🌈 2.2 实现Runnable接口并重写run()方法
🌈 注意:多个线程的写法
🌾3、多线程编程的意义
🌾4、线程的类与常用方法
🌈 4.1 构造方法
🌈 4.2 Thread类的几个常见属性
🌾5、线程的状态
🌾 前言
在学习之前,我们先简单的了解一下计算机中的常见的概念。
1、冯诺依曼结构体系
现代的计算机大多遵循冯诺依曼体系结构。主要由运算器,控制器,存储器,输入设备,输出设备组成。
2、操作系统
操作系统是一组做计算机资源管理系统的统称。常见的操作系统由Windows系列,Linux系列,OSX系列,ISO系列,Android系列。操作系统的作用主要有:(1)向上为应用程序提供一个稳定的运行环境;(2)向下管理所有的硬件设备;(3)为用户提供一个人机交互的界面。
3、进程
每一个运行起来的程序(软件),操作系统都会以进程的形式将他们管理起来。在操作系统中,每一个进程被描述为一个PCB(进程控制块抽象)。一个PCB中主要包括:PID,内存指针,文件描述符表,进程的优先级,进程的状态,上下文信息和记账信息。
CPU分配 -> 进程调度:假设大部分场景下都是单CPU单核的计算机。操作系统对于CPU资源的分配,采用的是时间模式:不同的进程在不同的时间段去使用CPU资源。
内存分配 ->内存管理:操作系统对内存资源的分配,采用的是空间模式:不同的进程使用内存中的不同区域,互相之间不会干扰。
4、虚拟内存空间
进程启动之后会申请内存空间,正常情况下是没有问题的。但是可能会出现一些特殊情况,比如C++中的野指针等。因此实际中,操作系统中有一个模块叫做MMU,MMU会给每个进程分配一个虚拟内存地址,这个地址和真实的物理内存之间建立映射关系。当进程越界访问内存空间时,MMU直接会进行拦截,起到了一个校验的作用,不会对真实的物理内存造成影响。
存在的问题:但是由于MMU的限制,导致不同进程之间不能够互相访问内存,所以如何实现进程之间的数据共享,从而达到“信息交换”的需求?针对这一问题,进程间通信的需求应运而生。目前,主流操作系统提供的进程通信机制主要有:管道,共享内存,文件,网络,信号量,信号。
问题:并发与并行的区别?
在一个处理器上不停横向的轮动叫做并发,在多个处理器上执行叫做并行。通俗解释就是:并发是:一会干这件事,一会干那件事,但其实同一时刻只能干一件事;并行是:一边干这件事,一边干那件事,在同一时刻干多件事,真正意义上的同时进行,在编程方面,我们统一称为并发编程。
🌾 了解线程
🌈1.1 线程是什么?
一般多进程用来处理一个很大或者很复杂的任务。当进程启动的时候,需要申请内存,申请文件资源,将PCB加入到链表中;当进程结束的时候,需要释放文件,释放内存,从链表中删除PCB。但是在这个过程中申请和释放资源的操作是十分耗时的。因此为了解决资源消耗的问题,提出了一个轻量级进程的概念(线程)。在创建线程的时候使用的是进程创建时申请到的所有资源,线程只需要关注要处理的任务即可。
举个例子:比如张三设计了一些皮包,他要去开一个工厂。他需要做以下一些事情:、
(1)去工业园区申请地皮,水电,仓库等(花了一个月的时间和几十万的钱);
(2)修建厂房和基础设施(花了几个月和几十万的钱);
(3)购买生产设备和原材料,招工(花了一周和几万块钱);
(4)开始生产。
主要关注两者之间的对应关系!
🌈1.2 一些基本问题
❓ 问题1:进程与线程之间的区别?(面试题)
(1)对于一个进程而言,必然会有一个线程(主线程);
(2)进程是申请系统资源的最小单位,线程是CPU调度的最小单位;
(3)进程之间互不影响,线程使用的是进程统一申请来的资源,线程之间可以相互影响。
❓ 问题2: 使用多线程编程的原因?
(1)为了充分利用CPU的资源
(2)利用轻量级进程的特性来减少系统性能的开销:
线程创建的效率比进程高;
线程销毁的效率比进程高;
线程调度的效率比进程高
❓ 问题3:使用多线程存在的问题?
当线程数量增大到一定程度之后,可能会给CPU调度带来负担,出现资源抢夺的问题,就会出现一些线程不安全的现象。
🌾2、创建线程的方式
🌈 2.1 继承Thread类
三部曲:继承Thread来创建一个线程类 -> 创建MyThread的实例 -> 使用start()方法启动线程
(1)正常写法
public class Demo1_createThread {public static void main(String[] args) {Thread t = new MyThread();t.start();while (true){System.out.println("main thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class MyThread extends Thread{@Overridepublic void run() {while (true){System.out.println("这里是线程运行的代码");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
(2)使用匿名内部类的写法
public static void main(String[] args) {//匿名内部类的形式Thread t = new Thread(){@Overridepublic void run() {while (true){System.out.println("生产生产生产...金币金币金币...+1+1+1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};//启动进程t.start();}
🌈 2.2 实现Runnable接口并重写run()方法
三部曲:实现Runnable接口 -> 创建Thread类实例,将Runnable对象作为其目标参数 -> 使用start()方法启动线程
注意:在调用start()方法后,JVM会调用系统API,并在系统中生成一个PCB来执行run()方法中的代码。
(1)正常写法
public class Demo2_createRunnable {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread t = new Thread(myRunnable);t.start();while (true){System.out.println("main thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class MyRunnable implements Runnable{@Overridepublic void run() {while (true){System.out.println("这是线程运行的代码");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
注意:两个线程并不是有序的交替运行,因为CPU的调度是随机的。线程的执行是“抢占式执行”。
(2)使用匿名内部类的写法
public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true){System.out.println("生产皮包,金币+1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});//开启线程t.start();}
(3)使用Lambda写法
public static void main(String[] args) {//Lambda表达式的使用条件就是该接口必须是一个函数式接口//比如Runnable接口就是一个 FunctionalInterface:所谓的函数式接口就是里面只有一个方法。比如(Runnable接口)中只有一个run();//怎么用Lambda表达式呢?()里写run方法的所有参数名,不需要参数类型,然后一个->即可。后面是正常的函数的功能实现Thread t = new Thread(()->{while (true){System.out.println("生产皮包,金币+1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//开启线程t.start();}
❓ 问题:使用Runnable 定义任务的好处?
(1)解耦,将定义线程与定义任务分开。(其中解耦的意思就是将不同的功能都给分开,如果要修改或者查找相应的功能的时候可以在指定的位置找)
(2)将创建线程与定义任务分开,以便修改代码时,可以统一修改。
🌈 注意:多个线程的写法
public class Demo3_multiRunnable {public static void main(String[] args) {//创建任务的对象MyRunnable1 myrunnable1 = new MyRunnable1();MyRunnable2 myrunnable2 = new MyRunnable2();//创建生产皮包的线程Thread t1 = new Thread(myrunnable1);Thread t2 = new Thread(myrunnable1);//创建生产皮鞋的线程Thread t3 = new Thread(myrunnable2);//启动线程t1.start();t2.start();t3.start();}
}
//描述生产皮包的任务
class MyRunnable1 implements Runnable{@Overridepublic void run() {System.out.println("生产皮包,金币+1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
//描述生产皮鞋的任务
class MyRunnable2 implements Runnable{@Overridepublic void run() {System.out.println("生产皮鞋,金币+1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
🌾3、多线程编程的意义
使用多线程编程主要是为了充分利用CPU资源,提高程序的运行效率。
🌰:现在我们分别对两个变量进行10亿次的自增,分别使用两种方式:串行和并行。观察一下它们之间的时间差异。理论上来讲,并行应该是串行时间的一半。接下来我们俩看一下实际上是不是这样~
结论:从上可以看出并行时间确实小于串行时间,但并不是严格意义上的一半,这主要是因为每创建一个线程都是要消耗时间和资源的。同样的,我们将计算量变小一些,可以看到反而串行时间小于并行时间。因此我们要知道并不是所有的场景下使用多线程第一可以提升效率,具体是否需要使用多线程主要根据计算量来决定。
🌾4、线程的类与常用方法
🌈 4.1 构造方法
Thread类是JVM用来管理线程的一个类。
🌈 4.2 Thread类的几个常见属性
(1) 获取线程名:Thread.currentThread().getName();
(2)是否后台线程:isDaemon()
isDaemon() 默认为false(前台线程),如果要创建后台线程则传入true,手动设置为后台线程。其中在main方法结束之后,线程会自动结束。
❓ 问题:前台线程与后台线程的区别?
应用程度的主线程以及使用new Thread方式构造的线程都默认是前台线程。通过BeginXXX方法运行的线程都是后台线程,托管线程池中的线程都是后台线程。
前台线程与后台线程的主要区别就是:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。如果程序定义了一个不会完成的前台线程,主程序不会正常结束。
比如银行转账的业务,一定要用前台线程;统计微信步数这种处理容错率比较高的业务或者辅助功能等,就可以使用后台线程。
后台线程演示:
public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//第一次:在线程启动之前 设置线程为后台,观察线程的存活状态thread.setDaemon(true);System.out.println("在线程start之前设置为后台线程,此时的存活状态是:"+ thread.isAlive());//启动线程thread.start();//此时休眠是因为start之后,系统要去创建PCB,给系统一点时间能够创建成功Thread.sleep(2000);//第二次:在start线程之后,观察线程的存活状态System.out.println("在线程start之后观察线程的存活状态:"+thread.isAlive());System.out.println("main线程执行完成");System.out.println("此时线程是否存活:"+thread.isAlive());}
(3)是否存活 isAlive()指的是系统的线程PCB是否存活,并不是说我们new出来的Thread对象。
(4)是否被中断:停止或者终止当前现成的任务
方式1:通过设置中断标识:主要注意代码中的while(!isQuit) 和 isQuit = true这两点。
方式2:通过调用Thread类提供的interruped()方法来中断线程。isInterrupted()默认为false不中断。
注意看下面的写法:有两处改动,但是会抛出异常。
这主要是因为上面的两处改动都针对的是runnable状态中的异常处理,所以我们还要对 Sleep状态做处理:在catch中处理:有三种方式:
(5)等待一个线程 join():等待线程结束
(6)获取当前线程引用:Thread thread = Thread.currentThread();
(7)start()方法
❓ 问题:start()方法与run()方法的区别?(面试题)
start():申请一个真正的系统线程run() :定义线程要执行的任务。
直接调用run方法,并不会去申请一个真正的系统线程(PCB),只是一个普通的方法调用,是单独的调用对象的方法。调用start方法,JVM会调用本地方法去系统中真正的申请一个线程(PCB),并执行run方法中 的逻辑。
🌾5、线程的状态
注意:线程的状态指的是Thread对象(有自己的生命周期)的状态,并不是PCB的状态。主要有以下几种:
1、NEW :java对象创建好了,但是还没有调用start方法创建PCB。
2、RUNNABLE : 就绪和运行状态。可以随时调度到CPU中执行。
3、TERMINATED: 线程任务执行完成,PCB在操作系统中已经销毁,但是java对象还存在。
4、TIMED_WATING :等待一段时间,这个时间是有时间限制的等待(过时不候)。比如sleep(),常见的访问超时。
5、WAITING:没有时间限制的等待。
6、BLOCK:等待锁的时候进入的阻塞状态。
最后,知道 在系统中针对线程的两个调度是用两个队列来实现的。准备执行的线程在就绪队列中,随时等待CPU调度。当调用sleep,wait,join方法时,就会被移动到阻塞队列中。当休眠时间到了之后,就会被移动回就绪队列,重新参与CPU调度。
相关文章:

【JavaEE初阶】认识线程(Thread)
目录 🌾 前言 🌾 了解线程 🌈1.1 线程是什么? 🌈1.2 一些基本问题 🌾2、创建线程的方式 🌈 2.1 继承Thread类 🌈 2.2 实现Runnable接口并重写run()方法 🌈 注意…...

自动化运维工具一Ansible Roles实战
目录 一、Ansible Roles概述 1.1.roles官方的目录结构 1.2.Ansible Roles依赖关系 二、Ansible Roles案例实战 2.1.Ansible Roles NFS服务 2.2 Roles Memcached 2.3 Roles-rsync服务 一、Ansible Roles概述 之前介绍了 Playbook 的使用方法,对于批量任务的部…...

json 中有递归parentId节点转 c#实体类时如何处理
如果您有一个具有递归parentId节点的JSON数据,并且您需要将其转换为C#实体类,则可以使用以下方法: 创建一个类来表示JSON对象的节点,包括它的属性和子节点。 public class Node {public int Id { get; set; }public string Name …...

给大家介绍几个手机冷门但好用的小技巧
技巧一:拍照识别植物 手机的拍照识别植物功能是指在使用手机相机时,可以通过对植物进行拍照,并通过植物识别技术,获取植物的相关信息和资料。其主要优点如下: 方便实用:使用拍照识别植物功能,…...

2.3 定点乘法运算
学习目标: 如果我要学习定点乘法运算,我会按照以下步骤进行学习: 确定学习目标:明确学习定点乘法运算的目的和重点,以便有针对性地进行学习。 掌握基础知识:首先需要了解定点数和定点乘法的基础知识&…...

C++每日一练:打家劫室(详解动态规划法)
文章目录 前言一、题目二、分析三、代码总结 前言 这题目出得很有意思哈,打劫也是很有技术含量滴!不会点算法打劫这么粗暴的工作都干不好。 提示:以下是本篇文章正文内容,下面案例可供参考 一、题目 题目名称: 打家…...

Wireshark使用
Capture Filters 语法 <Protocol name><Direction><Hosts><Value><Logical operations><Expressions> e.g 1.tcp src port 443 只抓取来源端口是443的tcp数据 2.not arp 不获取arp数据 3.port 80 获取端口是80的数据,不指…...

这才是 SpringBoot 统一登录鉴权、异常处理、数据格式 的正确姿势
本篇将要学习 Spring Boot 统一功能处理模块,这也是 AOP 的实战环节 用户登录权限的校验实现接口 HandlerInterceptor WebMvcConfigurer 异常处理使用注解 RestControllerAdvice ExceptionHandler 数据格式返回使用注解 ControllerAdvice 并且实现接口 Response…...

Java面试题总结 | Java面试题总结6-MYSQL模块(持续更新)
Mysql 文章目录 Mysql关系型数据库和非关系型数据库的区别什么是ORM?-**mybatis**如何评估一个索引创建的是否合理?Count函数执行效果上:执行效率上:count(主键)和count(列名) 数据库的三大范式Mysql中char和varchar的区别数据库设计或者功能…...

Linux命令集(Linux文件管理命令--mv指令篇)
Linux命令集(Linux文件管理命令--mv指令篇) Linux文件管理命令集(mv指令篇)2. mv(move)1. 文件移动2. 递归移动目录3. 文件目录重命名4. 强制移动5. 备份覆盖的目标文件6. 试探性移动7. 显示移动进度8. 补集操作9. 修改文件的权限…...

不一样的 Git 之间 | GitLab vs GitHub vs Gitee vs GitCode
Git仓库对比:GitLab vs GitHub vs Gitee vs GitCode 在软件开发中,版本控制是必不可少的工具之一。Git作为目前最为流行的版本控制系统,也逐渐成为了开发者们的标配。但是,如何选择一个合适的Git仓库来存储您的代码呢?…...

海尔牵头IEEE P2786国际标准通过Sponsor投票并连任工作组主席
01 海尔牵头IEEE P2786国际标准 通过Sponsor投票 并连任工作组主席 海尔牵头制定的全球首个服装物联网国际标准IEEE P2786《Standard for General Requirements and Interoperability for Internet of Clothing》通过Sponsor投票,标志着该国际标准草案得到了行业…...

倾斜摄影超大场景的三维模型的顶层合并的纹理压缩与抽稀处理技术分析
倾斜摄影超大场景的三维模型的顶层合并的纹理压缩与抽稀处理技术分析 倾斜摄影超大场景的三维模型的顶层合并需要对纹理进行压缩和抽稀处理,以减小数据量和提高数据的传输和展示性能。以下是一种常用的纹理压缩和抽稀处理技术: 1、纹理图集 纹理瓦片化…...

linux命令之iostat详解
iostat 监视系统输入输出设备和CPU的使用情况 推荐Linux命令在线工具:linux在线查询工具 补充说明 iostat命令 被用于监视系统输入输出设备和CPU的使用情况。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,ios…...

【C++】程序员必备知识:认识类与对象
【C】程序员必备知识:认识类与对象 ①.面向过程和面向对象②.类的引入③.类的定义Ⅰ.定义方式Ⅱ.命名规则建议: ④.类的访问限定符及封装Ⅰ.访问限定符Ⅱ.封装 ⑤.类的作用域⑥.类的实例化⑦.类的对象大小计算Ⅰ.如何计算?Ⅱ.类对象存储方式Ⅲ…...

python基础实战5-python基本结构
1 if语句 if语句是用来进行判断的,其使用格式如下 if 要判断的条件: 条件成立时,要做的事情 案例一: age 30 print("------if判断开始------") if age > 18:print("我成年了") print("------if判断…...

移动端异构运算技术 - GPU OpenCL 编程(基础篇)
一、前言 随着移动端芯片性能的不断提升,在移动端上实时进行计算机图形学、深度学习模型推理等计算密集型任务不再是一个奢望。在移动端设备上,GPU 凭借其优秀的浮点运算性能,以及良好的 API 兼容性,成为移动端异构计算中非常重要…...

QString类方法和变量简介(全)
QString类方法和变量简介 操作字符串(|append|insert|sprintf|QString::arg()|prepend|replace|trimmed|simplified)查询字符串(startsWith|endsWith|contains|localeAwareCompare|compare)字符串转换 标准C提供了两种字符串:一种是C语言风格的以"\0"字符…...

中移链控制台对接4A平台功能验证介绍
中移链控制台具备单独的注册登录页面,用户可通过页面注册或者用户管理功能模块进行添加用户,通过个人中心功能模块进行用户信息的修改和密码修改等操作,因业务要求,需要对中移链控制台的用户账号进行集中管理,统一由 4…...

必知的Facebook广告兴趣定位技巧,更准确地找到目标受众
在Facebook广告投放中,兴趣定位是非常重要的一环。兴趣定位不仅可以帮助我们找到我们想要的目标受众,还可以帮助我们避免一些常见的坑。今天,就让我们一起来看看必知的Facebook广告兴趣定位技巧,更准确地找到目标受众。 1.不要只关…...

【MySQL】慢查询+SQL语句优化 (内容源自ChatGPT)
慢查询SQL语句优化 1.什么是慢查询2.优化慢查询3.插入数据优化5.插入数据底层是什么6.页分裂7.页合并8.主键优化方式10.count 优化11.order by优化12.group by 优化13.limit优化14.update 优化15.innodb 三大特征 1.什么是慢查询 慢查询是指执行SQL查询语句所需要的时间较长&a…...

HashMap底层源码解析及红黑树分析
HashMap线程不安全,底层数组链表红黑树 面试重点是put方法,扩容 总结 put方法 HashMap的put方法,首先通过key去生成一个hash值,第一次进来是null,此时初始化大小为16,i (n - 1) & hash计算下标值&a…...

科技云报道:一路狂飙的ChatGPT,是时候被监管了
科技云报道原创。 即使你过去从不关注科技领域,但近期也会被一个由OpenAI(美国的一家人工智能公司)开发的人工智能聊天机器人“ChatGPT”刷屏。 与上届“全球网红”元宇宙不同,这位新晋的“全能网友”似乎来势更加凶猛。 互联网…...

第四十四章 管理镜像 - 传入日记传输率
文章目录 第四十四章 管理镜像 - 传入日记传输率传入日记传输率镜像数据库状态 第四十四章 管理镜像 - 传入日记传输率 传入日记传输率 在备份和异步成员的镜像成员状态列表下方,自上次刷新镜像监视器以来日志数据从主服务器到达的速率显示在该成员的传入日志传输…...

加密解密学习笔记
加密种类 对称加密,分组对称加密算法 加密算法 AES(Advanced Encryption Standard)高级加密标准 DES(Data Encryption Standard)数据加密标准 3DES/Triple DEA (Triple Data Encryption Algorithm) 三重数据加密算…...

Spring 属性填充源码分析(简单实用版)
属性填充 属性填充只有 3 种方式 根据名称填充 根据类型填充 思考什么时候会出现呢??? 多见于第三方框架与 Spring集成,举例:Mybatis 与 Spring集成,把 Mapper 接口注册为 BeanDefinition 时候就指定了自…...

【机器学习分支】重要性采样(Importance sampling)学习笔记
重要性采样(importance sampling)是一种用于估计概率密度函数期望值的常用蒙特卡罗积分方法。其基本思想是利用一个已知的概率密度函数来生成样本,从而近似计算另一个概率密度函数的期望值。 想从复杂概率分布中采样的一个主要原因是能够使用…...

三角回文数+123
三角回文数:用户登录 问题描述 对于正整数 n, 如果存在正整数 k 使得 n123⋯kk(k1)/2, 则 n 称为三角数。例如, 66066 是一个三角数, 因为 66066123⋯363 。 如果一个整数从左到右读出所有数位上的数字, 与从右到左读出所有数位 上的数字是一样的, 则称这个数为…...

JAVA常用的异步处理方法总结
前言 在java项目开发过程中经常会遇到比较耗时的任务,通常是将这些任务做成异步操作,在java中实现异步操作有很多方法,本文主要总结一些常用的处理方法。为了简化,我们就拿一个实际的案例,再用每种方法去实现…...

GitLab统计代码量
gitlab官方文档:https://docs.gitlab.com/ee/api/index.html 1、生成密钥 登录gitlab,编辑个人资料,设置访问令牌 2、获取当前用户所有可见的项目 接口地址 GET请求 http://gitlab访问地址/api/v4/projects?private_tokenxxx 返回参数 …...