JavaEE之多线程

一.认识线程

1.多进程实现并发编程的不足之处:

引入多个进程的核心:实现并发编程(c++的CGI技术就是通过多进程的方式实现的网站后端开发)。因为现在是一个多核cpu的时代,并发编程就是刚需。多进程实现并发编程,效果理想。但很多进程进行编程的模式也有缺点:就是进程太重量,效率不高(创建一个进程,消耗的时间很多,销毁一个进程,调度一个进程,也都需要消耗时间小号空间等,也就是都消耗在申请资源上)。

进程是资源分配的基本单位,它的分配主要是通过一定的数据结构。以分配内存为例:操作系统内部有一定的数据结构,会把空闲的内存分块管理,当申请内存时,系统就会从这样的数据结构中找到大小合适的空闲内存,返回给对应的进程。此处通过数据结构虽然可以一定程度上提高效率,但当管理的空间变多,就还会是费时费力的操作。

也就是说,如果需要频繁的创建销毁进程,这个开销就不可以忽视了。

2.引入线程

进程包含线程

由于上述缺点,我们引入了线程(也叫轻量级进程)。它不可独立存在,依附于进程。进程包含一个或多个线程,也就是说,一个进程至少有一个线程,负责执行代码完成工作,也可以根据需要,创建更多线程,从而实现并发编程。

进程与线程的关系,就好比剧组与演员的关系,剧组是一个进程,演员是多个线程

线程的结构

我们在讲进程时说的进程的调度,其实都是基于“一个进程只有一个线程”,实际上,一个进程可以有多个线程,可独立进行调度。每个进程都有自己独立的pid,内存指针,文件描述符表,状态,优先级,上下文,记账信息等。

3.线程是调度执行的基本单位

上述线程的结构决定了现成的特点:

1.每个线程都可以独立去cpu上面执行调度

2.同一个进程的多个线程之间,公用同一份内存空间和文件资源。也就是说,创建新的线程时,不用重新申请资源,而是直接复用之前分配给进程的资源,这就省去了资源分配到开销,所以创建效率更高,更轻量。

4.进程与线程的区别

1.进程包含线程。

2.进程和线程都是用来实现并发编程场景的,但线程比进场更轻量更高效

3.同一个进程的线程之间,共用同一份资源(内存+硬盘)(这表现在:后面写代码的时候,可以直接访问同一个变量,就能实现线程间的通信),省去了申请资源的开销。

4.进程和进程之间有独立性,一个进程挂了,不会影响其他进程。但同一个进程的线程和线程之间可能会相互影响(线程安全问题+线程出现异常)

5.进程是资源分配的基本单位,线程是调度执行的基本单位。

5.线程也不能无限增多

起初,增加线程的数目可以提高进程的效率。但无线增加,会使调度开销变大,就降低了进程的效率。

而且,当线程增多时,可能会引起冲突,这就是线程不安全问题

还有,如果一个线程出现异常,就会抛出异常,如果不及时解决,就会中断程序进行,也就是终端进程,这就导致其他线程消亡了。

二.多线程编程

线程是操作系统的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用(比如Linux的pthread库)。Java标准库中的Thread类可以视为是对操作系统提供的API进行了进一步的封装和抽象

1.法一:继承Thread类,重写run方法

run方法是一个自定义线程的入口方法。每个线程都是一个独立的执行流,可独立执行一系列逻辑。那么一个线程跑起来是从哪里开始执行?从它的入口方法。运行Java程序,就是跑一个Java进程,Java进程里面至少有一个线程,也称为主线程,这个主线程的入口方法就是main方法。

这是我们自定义的一个线程,要想让它跑起来,就先得创建线程:

run是一个线程的入口,所以我们调用了run方法

当加上循环时,会不会俩个语句都打印呢?

执行发现,竟然没有都打印,继续让代码执行,我们打开jconsole.exe来看看是否是俩个进程都执行:

发现只有主线程在执行,也就是说,调用了t.run之后,自定义线程没有真正执行。这是为什么呢?不是说一个java程序就是一个进程吗?哪个进程里面的线程不都是同时执行吗?

别着急,thread类中还有一个start方法,我们来试一试:

这回,我们不仅找到啦main,而且还找到了Thread-0,这就说明,调用了start方法,线程才真正创建,否则,MyThread兑现只是一个普通的对象,run方法也只是一个普通的方法。

run与start的区别:

start和run都是thread的成员

而run只是描述了线程的入口,在主线程中如果单纯调用t.run,就不会真正创建线程,而只有一个主线程在工作。但start就不一样了,调用了start,才能真正调用系统的API,在系统内核中创建线程,一旦创建了线程,线程就会自动调用run方法,去执行内部的代码。

sleep方法:

如何让线程变慢?可以用到Thread里面的sleep静态方法

它会抛出受查异常,所以注意处理异常,如下:

注意,调用了sleep方法后,在main方法中可以throws向上抛出,那为什么在run方法中不能添加异常到方法标签呢?因为这个run方法是重写自父类的方法,父类方法没有填加异常到方法标签,重写的时候自然也不能啦!

2.法二:实现Runnable,重写run方法

thread类实现了runnable接口,所以我们创建自定义线程的时候也可以实现runnable接口

注意,MyRunnable这个类就表示待会儿创建的线程可以运行,然后再实例化Thread对象时,讲MyRunnable对象传入进构造方法即可:

这是我们使用的构造方法,上面的英文解释了形参target:当线程启动时,调用这个target的run方法。如果这个target为null,那么创建的线程thread就什么都不做。

法二相比于法一的优点:

1.Java不支持多继承,如果继承了Thread,就不能再继承其他类;而使用了Runnable就可以再继承其他类了

2.解耦合!!!

创建一个线程,需要俩个关键操作:一个是明确线程要执行的任务,另一个是调用系统api来创建线程。

而任务本身,不一定和线程的概念强相关,这个任务只是单纯的执行一段代码,它是使用单线成还是多线程还是其他方式,都与任务无关。所以就可以把任务单独从线程中提取出来,然后就可以随时把代码改写成用其他方式执行(现在像用单线程来执行这个任务,肯议会的需求是用多线程执行)。

3.法三:继承Thread,重写run,但使用匿名类

4.法四:实现Runnable,但是用匿名内部类

5.法五:直接创建Runnable对象,后面重写run

6.法六:用lambda表达式

具体怎么使用Lambda表达式请看文章http://t.csdnimg.cn/m2bqG。

三.Thread类及其常见属性和方法

1.常用属性及其获得

ID:线程的身份标识   getID()

名称:getName()

讲到这个,就不得不说一说Thread中的构造方法:其中常用的就有可以传入线程名字的方法

状态:getState()

优先级:getPriority()

是否为后台线程:isDaemon()

注意,线程分为后台线程(也叫守护线程)和前台线程。一个java进程中,若前台线程没有结束,那么这个进程就一定没有结束,但后台线程没有结束不会影响整个线程的结束。如下举例:

有俩个线程,线程1先休息5秒然后再打印,线程2直接打印,当没有调用isDaemon时,这俩个线程都会执行:

但当调用了setDaemon将daemon改成true时(表示该线程被改成了后台线程),会有如下结果:

由于线程1要休眠5秒,在这期间主线程和线程2已经结束,它们都默认是前台线程,所有前台线程结束后,不管后台线程是否结束,进程都要结束

所以说,创建线程默认是前台线程,只有通过setDaemo主动将daemon改成true,才能编程后台线程

是否存活:isAlive()

Thread对象的生命周期,要比系统内核中的线程的生命周期更长一些,也就是说,Thread对象还在,但内核线程已被销毁。什么时候线程就没了?就是回调方法执行完毕后

看下面的代码,t线程只休眠2秒,而主线程休眠3秒,当主线程休眠完毕后,t线程一定已经执行完毕,所以会有如下结果

注意,true和“执行开始”这俩条日志谁先打印可不一定,因为线程是并发执行的,调度顺序无法确定,但大概率是先打印true,因为线程对象的创建也要消耗时间

是否被中断:isInterrupted()

2.启动一个线程

关键就是start方法,前面已经提到了,start方法内部会调用系统的API,从而在系统内核中创建线程

而run方法,只是单纯的描述了一个线程要干什么,要执行啥内容,它会在start创建好线程之后自动被调用

所以说start和run的本质区别是是否在系统内核中创建一个线程。

3.打断(终止)一个线程

要想终断一个线程,就要想办法让run方法尽快执行完毕

法一:手动创建一个标志位,作为run方法结束的条件

要明确,往往一个线程迟迟不结束,主要是有while循环,我们要想办法让循环结束,代码如下:

主线程休眠5秒后,将isQuit改成true,然后创建的线程就结束了

但有一个问题,当前,上述代码是使用了一个成员变量作为标志位,那我可不可以用局部变量作为标志位呢?答案是不可以!!!

上面我们使用的是lambd表达式,涉及到了变量的捕获,它只能捕获被final修饰的变量或者未被进行修改的变量!!这在lambda表达式一文中有详细解释。如下:用了局部变量后,由于待会要进行修改,所以会报错:

法二:使用Thread类中现成的标志位

上述使用手动创建的标志位的方法不够完善,首先,我们得自己创建标志,其次,当新县城还在休眠而主线程已经把标志改为true时,新线程无法立刻终止,而是得等休眠完指定事件后再终止,只让终止处理不够及时,所以有了下面的方法:

我们一个一个来解释:首先调用Thread.currentThread()可以获取到当前正被调度的线程,也就是t,再调用isInterrupted判断是否被终止,如果没被终止,就执行while,在while中,每休眠1秒就打印一次。然后是主线程,在休眠了5秒后,调用interrupt()手动终断线程。这里就可以看出与自定义标志位的区别啦,用了interrupt后,即使进程正在sleep,也能立马终止,这是因为sleep有可能会抛出InterruptedException这样的异常(就是说,正常情况下,sleep会一直持续到休眠够定义的时间,但当遇到interrupt将线程设定为被打断状态后,他就会抛异常,从而终止线程,结果如下:

欸?为什么已经抛出异常了,但还是继续执行了线程?

这是因为sleep抛出异常之后,会再次把标志位清除,导致线程又继续执行起来。为啥这么设定?就是为了让程序员自己能自己决定接下来要干什么,或者说如何处理。

接下来又三种处理方式:

1.假装没看见,让线程继续执行

2.加上一个break,让线程立即结束

3.做一些其他工作,完成后再结束

也就是再在break之前加一些其他代码,执行完再结束

4.等待一个线程

调用join方法,让一个线程,等待另一个线程完全执行结束后,再继续执行,代码如下:

在t线程中进行5次循环,相当于执行了5秒多,调用了join之后,主线程会在过了五秒之后再向后执行,如下:

总结一下:若线程t正在执行,调用了join的哪个线程就会触发阻塞;若线程t已经执行完了,那么调用了join的线程就直接返回了,不会涉及阻塞。

当未设置时,join默认是死等,但我们实际还可以自定义一个等待时间,那么超过这个时间就不会继续等待了,如下:

第二种就时更精细一点

5.获取当前线程的引用

就是我们用到的Thread.currentThread()

打印出来的名字就是main

四.线程的状态

线程的状态是一个枚举类型Thread.State,(State是一个枚举类,它是定义在Thread类里面的)我们可以如下来观察线程的所有状态:

1.NEW

表示thread对象已经拥有,但start方法还没屌用(就是说,已经有了对象,但还没在系统内核中创建线程)

这时打印出来的就是NEW

2.RUNNABLE

就绪状态,线程已经在cpu上执行或者线程正在排队等待cpu的调度

这时打印出来的就是RUNNABLE

3.WAITING

阻塞状态,由于wait这种不固定时间的方式产生的阻塞状态(之后会进行讲解)

4.TIMED-WAITING

阻塞状态:由于sleep这种固定时间的方式造成的阻塞状态

5.BLOCKED

阻塞状态:由于竞争导致的阻塞状态(之后会进行讲解)

6.TERMINATED

表示Thread对象还在,但内核中的线程已被销毁

这时打印的就是TERMINATED

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

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

相关文章

达梦、金仓、南大、瀚高、优炫:从社区建设看企业技术自信心

正文约950字,预计阅读时间2分钟 国产技术厂商在面对自身产品问题时,往往保持回避态度,不愿公之于众,主要原因有2方面: 1,产品技术层面问题较多,如某些根本性缺陷难以攻克,或问题发…

java找工作之Mybatis(入门及xml配置相关)

Mybatis 学习Mybatis就要学会查看官网&#xff0c;官网地址如下&#xff1a;<MyBatis中文网 > 1、简介 1.1什么是Mybatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取…

【Vue3】3-6 : 仿ElementPlus框架的el-button按钮组件实

文章目录 前言 本节内容实现需求完整代码如下&#xff1a; 前言 上节,我们学习了 slot插槽&#xff0c;组件内容的分发处理 本节内容 本小节利用前面学习的组件通信知识&#xff0c;来完成一个仿Element Plus框架的el-button按钮组件实现。 仿造的地址&#xff1a;uhttps://…

预充电阻器选型报告

1. 客户基础条件 预充时间 t≤200ms &#xff0c;电容 C1280uf &#xff0c;电池包最高电压 U410V&#xff0c;预充深度 98% &#xff0c;30 秒内连续预充 15 次。 1.1 现选型号 现选EAK预充电阻额定功率 60W&#xff0c;标称阻值为 35Ω&#xff0c;在 此条件下单次预充…

Unity 协程(Coroutine)到底是什么?

参考链接&#xff1a;Unity 协程(Coroutine)原理与用法详解_unity coroutine-CSDN博客 为啥在Unity中一般不考虑多线程 因为在Unity中&#xff0c;只能在主线程中获取物体的组件、方法、对象&#xff0c;如果脱离这些&#xff0c;Unity的很多功能无法实现&#xff0c;那么多线程…

红黑树的简单介绍

红黑树 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出俩倍&#x…

Python类 __init__() 是一个特殊的方法

设计者&#xff1a;ISDF工软未来 版本&#xff1a;v1.0 日期&#xff1a;2024/3/5__init__() 是一个特殊的方法 类似c# C的构造函数 两头都包含两个下划线&#xff0c;这是约定&#xff0c;用于与普通的函数保持区分class User:用户类def __init__(self,first_name,last_name):…

Linux 运维:CentOS/RHEL防火墙和selinux设置

Linux 运维&#xff1a;CentOS/RHEL防火墙和selinux设置 一、防火墙常用管理命令1.1 CentOS/RHEL 7系统1.2 CentOS/RHEL 6系统 二、临时/永久关闭SELinux2.1 临时更改SELinux的执行模式2.2 永久更改SELinux的执行模式 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;…

Finetuning Large Language Models: Sharon Zhou

Finetuning Large Language Models 课程地址&#xff1a;https://www.deeplearning.ai/short-courses/finetuning-large-language-models/ 本文是学习笔记。 Goal&#xff1a; Learn the fundamentals of finetuning a large language model (LLM). Understand how finetu…

STM32(16)使用串口向电脑发送数据

发送字节 发送数组 发送字符和字符串 字符&#xff1a; 字符串&#xff1a; 字符串在电脑中以字符数组的形式存储

ElasticSearch之分布式模型介绍,选主,脑裂

写在前面 本文看下es分布式模型相关内容。 1&#xff1a;分布式模型 1.1&#xff1a;分布式特征 支持水平扩展&#xff0c;可以存储PB级别数据&#xff0c;每个就能都有自己唯一的名称,默认名称时elasticsearch&#xff0c;可以通过配置文件&#xff0c;如cluster.name: my…

PowerBI怎么修改数据库密码

第一步&#xff1a;点击转换数据 第二步&#xff1a;点击数据源设置 第三步&#xff1a;点击编辑权限 第四步&#xff1a;点击编辑 第五步&#xff1a;输入正要修改的密码就可以了

STM32启动过程及反汇编

STM32从Flash启动的过程&#xff0c;主要是从上电复位到main函数的过程&#xff0c;主要有以下步骤&#xff1a; 1.初始化堆栈指针 SP_initial_sp&#xff0c;初始化 PC 指针Reset_Handler 2.初始化中断向量表 3.配置系统时钟 4.调用 C 库函数_main 初始化用户堆栈&#xf…

SAP HANA中PAL算法使用入门

1 应用场合 SAP HANA作为一款内存数据库产品, 使得数据常驻内存, 物理磁盘的存储作为数据备份与日志记录, 以防断电内存中数据丢失. 这种构架大大的缩短了数据存取的时间, 使得SAP HANA很”高速”. 在传统数据模型中,数据库只是作为存取数据一个工具,对于类似下图所示的应用, 客…

星瑞格数据库管理系统

一. 产品介绍 随着信息化的到来&#xff0c;数据安全成为保障信息化建设的一个关键问题&#xff1b;数据库作为信息化系统的基础软件其自身安全以及对数据的保障是至关重要。现阶段国内重要部门的信息系统存放着大量敏感数据&#xff0c;为了保障其数据的安全性&#xff0c;使用…

11、电源管理入门之Regulator驱动

目录 1. Regulator驱动是什么? 2. Regulator框架介绍 2.1 regulator consumer 2.2 regulator core 2.3 regulator driver 3. DTS配置文件及初始化 4. 运行时调用 5. Consumer API 5.1 Consumer Regulator Access (static & dynamic drivers) 5.2 Regulator Outp…

基于springboot+vue的美食烹饪互动平台

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Tomcat+Nginx的动静分离

1.反向代理多机 实验&#xff1a;Nginx要开启upstream(负载均衡)、location(url链接)、proxy_pass(反向代理) 配置&#xff1a;7-3做代理服务器&#xff1b;7-1 和 7-2做Tomcat服务器 关闭防火墙和selinux 1.准备配置 7-3安装nginx&#xff1b;7-1 和 7-2安装Tomcat&#xff…

章鱼网络 Community Call #18|Omnity 将首先支持 Runes 协议资产跨链

香港时间2024年2月8日12点&#xff0c;章鱼网络举行第18期 Community Call。 2024年&#xff0c;我们打开一个良好的局面&#xff1a;$NEAR Restaking 已经完成第三方审计&#xff0c;并且经过几次迭代&#xff0c;进入了正式稳定运行的阶段。更重要的是&#xff0c;我们宣布了…

对话框

1.焦点变更监听器 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto"xmlns:tools"http://schem…