第十一讲 多线程
多线程是提升程序性能非常重要的一种方式,也是Java编程中的一项重要技术。在程序设计中,多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。
1. 进程与线程
进程是指正在运行的程序,是系统进行资源分配和调度的基本单位。为了有效利用系统资源和提高运行效率,在一个进程中还可以有若干同时并发运行的线程。每一个进程都至少存在一个线程。
当一个Java程序启动时,就会产生一个进程,该进程中会默认创建一个线程,在这个线程上运行main()方法中的代码,这样的程序称为单线程程序。单线程程序中只有一个线程在运行,效率相对较低。好比是售票大厅只开设一个售票窗口,所有人只能在一个窗口排除买票,整个售票过程效率较低;如果同时开设多个售票窗口售票,则可以提高售票效率。编写的程序也是同样,可以通过创建多线程程序来提高程序运行效率。
2. 线程的创建
Java中提供了三种方式来实现多线程。
(1)Thread类实现多线程
Thread类是java.lang包下的一个线程类,可通过继承Thread类的方式来实现多线程,其使用方法是先创建一个Thread线程类的子类(子线程),同时重写Thread类的run()方法;然后创建该子类的实例对象,并通过调用start()方法启动线程。
【例11-1】通过Thread类实现多线程
运行结果如下图所示。
通过运行结果可以看到,两个线程对象交互执行了各自重写的run()方法。并不是按顺序先执行完第一个线程再执行第二个线程。
(2)Runnable接口实现多线程
虽然可以通过继承Thread类实现多线程,但这种使用方式有一定的局限性。因Java只支持单继承,如果某个类已经继承了其他类,就无法再继承Thread类来实现多线程。这时可通过实现Runnable接口的方式来实现多线程。
①使用实现Runnable接口的方式来实现多线程的主要过程如下。
②创建一个Runnable接口的实现类,重写接口中的run()方法。
③创建Runnable接口的实现类对象。
④使用Thread有参构造方法创建线程实例,并将Runnable接口的实现类的对象作为参数传入。
⑤调用线程实例的start()方法启动线程。
【例11-2】通过实现Runnable接口的方式来实现多线程
运行结果如下图所示。
Callable接口实现多线程
通过Thread类和Runnable接口实现多线程时,需要重写run()方法,但是由于该方法没有返回值,因此无法从多个线程中获取返回结果。为了解决这个问题,从JDK 5开始,Java提供了一个新的Callable接口,来满足这种既能创建多线程又可以有返回值的需求。
Callable接口实现多线程是通过Thread类的有参构造方法传入Runnable接口类型的参数来实现多线程,不同的是,这里传入的是Runnable接口的子类FutureTask对象作为参数,而FutureTask对象中则封装带有返回值的Callable接口实现类。
通过Callable接口实现多线程的过程如下。
①创建一个Callable接口的实现类,同时重写Callable接口的call()方法;
②创建Callable接口的实现类对象;
③通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象;
④使用参数为FutureTask类对象的Thread有参构造方法创建Thread线程实例;
⑤调用线程实例的start()方法启动线程。
【例11-3】通过Callable接口实现多线程
运行结果如下图所示。
Callable接口方式实现的多线程是通过FutureTask类来封装和管理返回结果的,该类的直接父接口是RunnableFuture。FutureTask类的继承关系如下图所示。
FutureTask本质是Runnable接口和Future接口的实现类,而Future则是用来管理线程执行返回结果的。其中Future接口中有5个方法来对线程结果进行管理,如表1所示。
表1 Future接口的方法
方法声明 | 功能描述 |
boolean cancel(boolean mayInterruptIfRunning) | 用于取消任务,参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行的任务 |
boolean isCancelled() | 判断任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true |
boolean isDone() | 判断任务是否已经完成,若任务完成,则返回true |
V get() | 用于获取执行结果,这个方法会发生阻塞,一直等到任务执行完毕才返回执行结果 |
V get(long timeout, TimeUnit unit) | 用于在指定时间内获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null |
3. 线程的生命周期
在Java中,任何对象都有生命周期,线程也不例外,它也有自己的生命周期。当Thread对象创建完成时,线程的生命周期便开始了。当线程任务中代码正常执行完毕或者线程抛出一个未捕获的异常(Exception)或者错误(Error)时,线程的生命周期便会结束。线程的整个生命周期分为6个状态,分别是NEW(新建状态)、RUNNABLE(可运行状态)、BLOCKED(阻塞状态)、WAITING(等待状态)、TIMED_WAITING(定时等待状态)和TERMINATED(终止状态)。线程的不同状态表明了线程当前正在进行的活动。程序中,通过一些操作,可以使线程在不同状态之间转换,如下图所示。
(1)NEW(新建状态)
创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,和其他Java对象一样,仅仅由JVM为其分配了内存,没有表现出任何线程的动态特征。
(2)RUNNABLE(可运行状态)
新建状态的线程调用start()方法,就会进入可运行状态。在RUNNABLE状态内部又可细分成两种状态:READY(就绪状态)和RUNNING(运行状态),并且线程可以在这两个状态之间相互转换。
RUNNABLE内部状态转换:
①就绪状态:线程对象调用start()方法之后,等待JVM的调度,此时线程并没有运行;
②运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行。
(3)BLOCKED(阻塞状态)
运行状态的线程因为某些原因失去CPU的执行权,会进入阻塞状态。阻塞状态的线程只能先进入就绪状态,不能直接进入运行状态。
线程一般会在以下两种情况时进入阻塞状态:
①当线程A运行过程中,试图获取同步锁时,却被线程B获取;
②当线程运行过程中,发出IO请求时。
(4)WAITING(等待状态)
当运行状态的线程调用了无时间参数限制的方法后,如wait()、join()等方法,就会转换为等待状态。
处于等待状态中的线程不能立即争夺CPU使用权,必须等待其他线程执行特定的操作后,才有机会争夺CPU使用权。例如调用wait()方法而处于等待状态中的线程,必须等待其他线程调用notify()或者notifyAll()方法唤醒当前等待中的线程;调用join()方法而处于等待状态中的线程,必须等待其他加入的线程终止。
(5)TIMED_WAITING(定时等待状态)
当运行状态中的线程调用了有时间参数限制的方法,如sleep(long millis)、wait(long timeout)、join(long millis)等方法,就会转换为定时等待状态。
处于定时等待状态中的线程不能立即争夺CPU使用权,必须等待其他相关线程执行完特定的操作或者限时时间结束后,才有机会再次争夺CPU使用权。例如,调用了wait(long timeout) 方法而处于等待状态中的线程,需要通过其他线程调用notify()或者notifyAll()方法唤醒当前等待中的线程,或者等待限时时间结束后也可以进行状态转换。
(6)TERMINATED(终止状态)
当线程的run()方法、call()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入终止状态。一旦进入终止状态,线程将不再拥有运行的资格,也不能再转换到其他状态,生命周期结束。
4. 线程的调度
程序中的多个线程是并发执行的,但并不是同一时刻执行,某个线程若想被执行必须要得到CPU的使用权。Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。
线程调度有两种模型,分别是分时调度模型和抢占式调度模型。分时调度是指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU时间片。抢占式调度是指让可运行池中所有就绪状态的线程争抢CPU的使用权,而优先级高的线程获取CPU执行权的概率大于优先级低的线程。
Java虚拟机默认采用抢占式调度模型,多数情况下不需要去关心它,在某些特定的需求下需要改变这种模式时可由程序来控制 CPU的调度。
(1)设置线程的优先级
在程序中如果要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大,而优先级越低的线程获得CPU执行的机会越小。
线程的优先级用1~10之间的整数来表示,数字越大优先级越高。除了可以直接使用数字表示线程的优先级,还可以使用Thread类中提供的三个静态常量(如表2所示)表示线程的优先级。
表2 Thread类的优先级常量
Thread类的静态常量 | 功能描述 |
static int MAX_PRIORITY | 表示线程的最高优先级,相当于值10 |
static int MIN_PRIORITY | 表示线程的最低优先级,相当于值1 |
static int NORM_PRIORIY | 表示线程的普通优先级,相当于值5 |
程序在运行期间,处于就绪状态的每个线程都有自己的优先级,例如main线程具有普通优先级。可以通过Thread类的setPriority(int newPriority)方法对其进行设置,该方法中的参数newPriority接收的是1~10之间的整数或者Thread类的三个静态常量。
【例11-4】线程优先级的设置
运行结果如下图所示。
说明:
虽然Java提供了10个线程优先级,但是这些优先级需要操作系统的支持,不同的操作系统对优先级的支持是不一样的,不能很好地和Java中线程优先级一一对应。
(2) 线程休眠
如果想要人为地控制线程执行顺序,使正在执行的线程暂停,将CPU使用权让给其他线程,这时可以使用静态方法sleep(long millis)。该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态,这样其他的线程就可以得到执行的机会。sleep(long millis)方法会声明抛出InterruptedException异常,因此在调用该方法时应该捕获异常,或者声明抛出该异常。
【例11-5】线程休眠
运行结果如下图所示。
5. 多线程同步
多线程的并发执行可以提高程序的效率,但是,当多个线程去访问同一个资源时,也会引发一些安全问题。如下例中的多线程售票程序。
【例11-6】多线程售票
运行结果如下图所示。
由运行结果可以看到,同一张票被出售了多次,这种现象是不应该出现的。出现这种问题的原因在于多个线程同时处理共享资源所导致的。为了解决这样的问题,只需要保证某个资源在同一时刻只能被一个线程访问即可,也即线程的同步,Java中提供了几种不同的线程同步机制。
(1)同步代码块
当多个线程使用同一个共享资源时,可以将处理共享资源的代码放置在一个使用synchronized关键字修饰的代码块中,这段代码块就被称为同步代码块。使用格式如下。
synchronized(lock){
// 操作共享资源代码块 ...
}
述代码中,lock是一个锁对象,可以是任意类型的对象,但多个线程共享的锁对象必须是相同的。锁对象的创建代码不能放到run()方法中,否则每个线程运行到run()方法都会创建一个新对象,这样每个线程都会有一个不同的锁。
【例11-7】利用同步代码块实现线程同步
运行结果如下图所示。
同步代码块的原理:
①当线程执行同步代码块时,首先会检查lock锁对象的标志位;
②默认情况下标志位为1,此时线程会执行Synchronized同步代码块,同时将锁对象的标志位置为0;
③当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后;
④锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码,这样循环往复,直到共享资源被处理完为止。
(2)同步方法
当把共享资源的操作放在同步代码块中时,便为这些操作加了同步锁。同样,也可以在方法前面使用synchronized关键字来修饰,被修饰的方法称为同步方法,可实现和同步代码块同样的功能,同步方法使用格式如下所示。
[修饰符] synchronized 返回值类型方法名([参数1,……]){
//方法体
}
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行。
【例11-8】使用同步方法实现线程同步
运行结果如下图所示。
相关文章:

第十一讲 多线程
多线程是提升程序性能非常重要的一种方式,也是Java编程中的一项重要技术。在程序设计中,多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。 1. 进程与线…...

VUE之路由Props、replace、编程式路由导航、重定向
目录 1、路由_props的配置 2、路由_replaces属性 3、编程式路由导航 4、路由重定向 1、路由_props的配置 1)第一种写法,将路由收到的所有params参数作为props传给路由组件 只能适用于params参数 // 创建一个路由器,并暴露出去// 第一步…...
windows安装ES
1. 下载ES 访问ES官网下载Download Elasticsearch | Elastic 2. 配置环境变量 ES_JAVA_HOME : D:\jdk-17.0.9 ES_HOME : D:\elasticsearch-8.17.1-windows-x86_64\elasticsearch-8.17.1 3. 添加一些ES的配置 <1>关闭ES安全认证 打开elasticsearch-8.17.1\config\e…...

论文速读|Multi-Modal Disordered Representation Learning Network for TBPS.AAAI24
论文地址:Multi-Modal Disordered Representation Learning Network for Description-Based Person Search 代码地址:未开源(2025.01.22) bib引用: inproceedings{yang2024multi,title{Multi-Modal Disordered Repres…...
小哆啦解题记:加油站的奇幻冒险
小哆啦解题记:加油站的奇幻冒险 小哆啦开始力扣每日一题的第十三天 https://leetcode.cn/problems/gas-station/description/ 在环形道路上,矗立着一串加油站,宛如等待挑战的谜题。这条路上的每个加油站都有一桶汽油,而开车到下一…...

【前端】CSS实战之音乐播放器
目录 播放器背景旋转音乐封面按钮进度条音量调节音乐信息按钮的效果JavaScript部分播放和暂停音乐切换音乐信息进度条 音量调节避免拖拽时的杂音音量调节条静音和解除静音 自动下一首实现一个小效果最终效果 播放器背景 <div class"play_box"></div>设置…...

Games104——渲染中光和材质的数学魔法
原文链接 渲染方程及挑战 挑战 对于任一给定方向如何获得radiance–阴影 对于光源和表面shading的积分运算(蒙特卡洛积分) 对于反射光多Bounce的无限递归计算 基础光照解决方案 Blinn-Phong模型: 简化阴影 最常见的处理方式就是Shadow M…...
impala增加字段,hsql查不到数据
impala增加字段,插入数据后直接查看文件有值,impala查询是有值的,但是hsq查出来就没有值! Parquet格式的表,在重命名表的列名,或新增列名后,查询重名的列数据时显示当前列所有值为NULL。 原因&a…...

SpringBoot项目中的异常处理
定义错误页面 SpringBoot 默认的处理异常的机制:SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会像/error 的 url 发送请求。在 springBoot 中提供了一个叫 BasicExceptionController 来处理/error 请求,然后跳转到…...

ComfyUI实现老照片修复——AI修复老照片(ComfyUI-ReActor / ReSwapper)尚待完善
AI修复老照片,试试吧,不一定好~~哈哈 2023年4月曾用过ComfyUI,当时就感慨这个工具和虚幻的蓝图很像,以后肯定是专业人玩的。 2024年我写代码去了,AI做图没太关注,没想到,现在ComfyUI真的变成了工…...
NLTK命名实体识别(NER)
命名实体识别(Named Entity Recognition, NER)是自然语言处理(NLP)中的一项核心技术,旨在从文本中识别出具有特定意义的实体,如人名、地名、组织名等。通过对文本的自动化处理,NER能够帮助计算机理解和组织大量的非结构化数据,为信息抽取、搜索引擎优化、数据分析等领域…...

【游戏设计原理】78 - 持续注意力
这个原理指出,人类的注意力通常只能维持7至10分钟,因此游戏设计需要根据这一规律进行优化。具体建议包括: 短时间段设计:将游戏体验分解成7到10分钟的任务或场景,以符合玩家的注意力节奏。引入新刺激:在注…...

Android设备:Linux远程lldb调试
更多内容:XiaoJ的知识星球 目录 一、环境准备1.1 安装llvm/NDK1.2 开启lldb-server服务1.3 lldb连接lldb-server 二、使用lldb调试Android native源码2.1 运行调试2.2 .lldbinit文件 下面介绍Android设备(Android手机为例),在Linu…...

多层 RNN原理以及实现
数学原理 多层 RNN 的核心思想是堆叠多个 RNN 层,每一层的输出作为下一层的输入,从而逐层提取更高层次的抽象特征。 1. 单层 RNN 的数学表示 首先,单层 RNN 的计算过程如下。对于一个时间步 t t t,单层 RNN 的隐藏状态 h t h_t…...

[Computer Vision]实验三:图像拼接
目录 一、实验内容 二、实验过程及结果 2.1 单应性变换 2.2 RANSAC算法 三、实验小结 一、实验内容 理解单应性变换中各种变换的原理(自由度),并实现图像平移、旋转、仿射变换等操作,输出对应的单应性矩阵。利用RANSAC算法优…...
【Vim Masterclass 笔记22】S09L40 + L41:同步练习11:Vim 的配置与 vimrc 文件的相关操作(含点评课内容)
文章目录 S09L40 Exercise 11 - Vim Settings and the Vimrc File1 训练目标2 操作指令2.1. 打开 vimrc-sample 文件2.2. 尝试各种选项与设置2.3. 将更改内容保存到 vimrc-sample 文件2.4. 将文件 vimrc-sample 的内容复制到寄存器2.5. 创建专属 vimrc 文件2.6. 对于 Mac、Linu…...
5.9 洞察 OpenAI - Translator:日志(Logger)模块的 “时光记录仪”
洞察 OpenAI - Translator:日志(Logger)模块的 “时光记录仪” 在开发和生产环境中,日志记录是确保应用程序正常运行和快速调试的核心机制之一。日志模块(Logger)用于记录应用程序的运行信息,包括错误、警告、调试信息、信息性事件等。通过日志,开发者可以实时监控程序…...

客户案例:电商平台对帐-账单管理(亚马逊amazon)
账单管理: 功能定义: 账单管理用于上传亚马逊(amazon)平台下载的原始账单数据,美国站、日本站、墨西哥站等账单模板直接进行数据上传,做到0调整,下载下来的账单数据无缝上传至对账平台-账单管…...
IP协议特性
在网络层中,最重要的协议就是IP协议,IP协议也有两个特性,即地址管理和路由选择。 1、地址管理 由于IPv4地址为4个字节,所以最多可以支持42亿个地址,但在现在,42亿明显不够用了。这就衍生出下面几个机制。…...

Kubernetes入门学习
kubernetes技术架构模型 一、kubernetes的Label标签 1.标签是以keyvalue的格式通过用户自定义指定,目的是将其加入到各种资源对象上来实现多维度的资源分组管理使其更方便的进行资源分配、调度、配置和部署管理工作。 2.标签可以结合Label Selector(标签选择器)查询…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...