JavaEE之多线程

news2024/11/15 17:54:00

一.认识线程

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.coloradmin.cn/o/1489797.html

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

相关文章

第 2 个 Java Web 应用工程(JSP JavaBean DB)(含源码)(图文版)

JavaBean 是一种符合特定约定的 Java 类,通常用于在 Java 应用程序中封装数据以及提供对数据的访问和修改方法。 本文示例:建立一个 Tomcat 工程,编写一个 JSP 页面,调用 JavaBean 访问数据库并显示到页面上,发布到 T…

Linux 之一:Linux 简介、客户端、安装

Linux简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。 Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统,其内核由林纳斯本纳第克特托瓦兹于…

GitLab EE 企业版破解

在当今数字化时代,软件开发与团队协作已经成为现代企业不可或缺的一部分。而在这个过程中,版本控制、协作和持续集成等工具的运用变得至关重要。GitLab作为一个领先的、完整的DevOps平台,为团队提供了一个集成的解决方案,使得软件…

MySQL8.0性能优化实战培训课-高阶数据库实战课程来袭!!

课程介绍 众多已经学习过MySQL 8.0 OCP认证专家的课程的同学们对 MySQL 8.0 的安装部署、体系结构、配置监控、用户管理、主从复制、系统运维、MGR等基础操作和动手实验有了一定的学习基础.很多学员反馈希望更进一步提升技术能力、解决工作中碰到的性能问题。 针对MySQL8.0的数…

运维随录实战(4)

添加账号并为账号赋予root权限 1,使用root账号添加一个普通账号 adduser test passwd test # 赋予密码 2,赋予root权限 修改/etc/sudoers文件,如果使用vi 命令打开提示仅只读,则使用 visudo命令打开 在root下面添加一行“test ALL=(ALL) ALL”,如下所示 3,将test账…

电脑不小心格式化了,怎么恢复?

在这个数字化时代,电脑已经成为我们日常生活和工作中不可或缺的工具。然而,有时我们可能会不小心格式化电脑硬盘,导致重要数据的丢失。那么,电脑不小心格式化了,怎么恢复? 别着急,在本篇攻略中&…

这里推荐一款unity3d人物动物控制器详细的等学会再写文章

unity3d Animal Controller 1.4.0a 动物NPC行为控制器 动物控制器(AC)是一个基于脚本架构的动画框架控制器。它适用于任何动物或人形角色的根运动或原地动画。 人和动物的各种动作都有; 小白必选、 我只是运行乐demo就感觉牛 demo路径:Asset…

Python实现ADTM工具判断信号:股票技术分析的工具系列(6)

Python实现ADTM工具判断信号:股票技术分析的工具系列(6) 介绍算法解释 代码rolling函数介绍完整代码data代码ADTM.py 介绍 ADTM(动态买卖气指标)是一种用于衡量市场买卖力量对比的指标。它通过计算动态买盘指标&#…

IDEA2023.2版本引用提示no usages的关闭和打开

相信很多下载使用高版本的IDEA的小伙伴们都会发现自己的代码主页会出现一个no usages的提示,如下图所示,其实这是IDEA所做出的来的一个辅助功能,目的是为了让使用者可以清楚的知道你在哪里被使用了;如图: 对于一些经常使用低版本的IDEA编程的小伙伴来说,这个功能的突然出…

S3---FPGA-A7板级电源硬件实战

视频链接 FPGA-A7板级电源硬件实战01_哔哩哔哩_bilibili FPGA-A7板级电源硬件实战 1、基于A7 板级的系统框图 2、基于A7 板级的电源设计细则 2.1、A7 FPGA功耗评估 Artix-7 FPGA电源有VCCINT, VCCBRAM, VCCAUX, VCCO, VMGTAVCC和VMGTAVTT。 2.1.1、A7 FPGA电源管脚 2.1.2…

GIS之深度学习05:VisualStudio安装教程

在安装CUDA前,建议先安装VisualStudio,以防报错 VisualStudio安装步骤简单,但时间较长。。。。。。 正文开始: VisualStudio官网:Visual Studio: IDE and Code Editor for Software Developers and Teams 点击右上角…

【观点】区块链的未来:分布式商业;企业的未来:分布式商业生态战略

本文内容摘自思二勋所著的《分布式商业生态战略》一书。 近两年,商业经济环境的不确定性越来越明显,市场经济受到疫情、技术、政策等多方因素影响越来越难以预测,黑天鹅事件时有发生。在国内外经济方面,国际的地缘政治对商业经济…

[LeetBook]【学习日记】寻找和为指定数字的连续数字

题目 文件组合 待传输文件被切分成多个部分,按照原排列顺序,每部分文件编号均为一个 正整数(至少含有两个文件)。传输要求为:连续文件编号总和为接收方指定数字 target 的所有文件。请返回所有符合该要求的文件传输组…

VM新建虚拟机

目录 一、前言二、下载镜像三、新建虚拟机 一、前言 上一篇文章我们介绍了VMware Workstation 15 Pro的安装与破解 ,这篇文章我们介绍一下新建虚拟机 二、下载镜像 Linux使用最多的是CentOS和Ubuntu,下面是下载地址 Centos7:https://www.centos.org/…

五、软考-系统架构设计师笔记-信息安全技术基础知识

信息安全技术基础知识 1、信息安全基础知识概述 信息安全的概念 信息安全包括 5 个基本要素: 机密性:确保信息不暴露给未授权的实体或进程。完整性:只有得到允许的人才能修改数据,并且能够判别出数据是否已被篡改。可用性:得到授权的实体在需要时可以…

WPF中如何设置自定义控件(二)

前一篇文章中简要讲解了圆角按钮、圆形按钮的使用,以及在windows.resource和app.resource中设置圆角或圆形按钮的样式。 这篇主要讲解Polygon(多边形)、Ellipse(椭圆)、Path(路径)这三个内容。 Polygon 我们先看一下的源码: namespace System.Windows.Shapes { pu…

第十七天-反爬与反反爬-验证码识别

目录 反爬虫介绍 基于身份识别反爬和解决思路 Headers反爬-使用User-agent Headers反爬-使用coookie字段 Headers反爬-使用Referer字段 基于参数反爬 验证码反爬 1.验证码介绍 2.验证码分类: 3.验证码作用 4.处理方案 5.图片识别引擎:ocr 6.使用打码平…

Window系统搭建feishu-chatgpt企业AI机器人并实现无公网ip远程连接

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话,在下面操作步骤中…

ubuntu系统(11):ubuntu20.04命令行安装vscode

目录 1、更新软件包索引,并且安装依赖软件 2、使用 wget 命令插入 Microsoft GPG key 3、启用vscode存储库 4、更新软件包并安装vscode 5、当前目录进入vscode 6、设置样式,添加所需扩展 最近换了个新的服务器,所以要重新配置服务器的…

python网络爬虫教程笔记(1)

系列文章目录 文章目录 系列文章目录前言一、爬虫入门1.爬虫是什么?2.爬虫工作原理3.爬虫基本原理4.工作流程5.HTTP请求6.HTTP响应7.HTTP原理:证书传递、验证和数据加密、解密过程解析8.Urllib.request库的使用9.TCP3次握手,4次挥手过程 总结…