06.JAVAEE之线程4

1.定时器

1.1 定时器是什么

定时器也是软件开发中的一个重要组件.
类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.
约定一个时间,时间到达之后,执行某个代码逻辑,
定时器非常常见,尤其是在进行网络通信的时候,

 需要有等待的最大时间,等待的最大时间通过定时器实现。

在标准库里,也是有现成的定时器的实现的 

主线程执行 schedule 方法的时候,就是把这个任务给放到 timer 对象中了,
于此同时,timer 里头也包含一个线程,这个线程叫做"扫描线程”,一旦时间到,扫描线程就会执行刚才安排的任务了。
仔细观察,可以发现,整个进程其实没有结束!! 就是因为 Timer 内部的线程,阻止了进程结束.
Timer 里,是可以安排多个任务的
import java.util.Timer;
import java.util.TimerTask;// 定时器
public class Demo25 {public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排了一个任务, 预定在 xxx 时间去执行.timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000");}}, 1000);System.out.println("程序启动!");}
}

 1.2 如何实现定时器

Timer timer = new Timer();

1.Timer 中需要有一个线程,扫描任务是否到时间,可以执行了

2.需要有一个数据结构,把所有的任务都保存起来.

【具体使用什么数据结构好呢?

假设使用数组(ArrayList),此时,扫描线程, 就需要不停的遍历数组中的每个任务判定每个任务是否都到达执行时间.

使用优先级队列,是更好的办法!!给 Timer 中添加的这些任务, 都是带有一个"时间’定是时间小的先执行。最先执行的就是时间最小的任务!!如果时间最小的任务,还没到时间呢,其他任务更不会到时间了!!优先级队列,可以使用 O(1)时间,来获取到时间最小的任务的!!

3.还需要创建,个类,通过类的对象来描述一个任务.(至少要包含任务内容和时间)

使用绝对的时间戳更为方便

对于优先级队列,要求里面的元素是可比较的,所以需要重写比较方法。

  • 如果发现队列为空,应该咋办呢?

好的办法,就是阻塞等待,等到队列不空为止 =>阻塞队列不就是这样的嘛~~
wait 要想使用, 需要搭配 synchronized,不能单独使用!!wait 进行的操作有三个:
1)释放锁 =>前提是, 先拿到锁,
2)等待通知
3) 通知到来之后, 唤醒,重新获取锁
这个方法,是一个线程中(比如主线程中),给队列添加元素

出现忙等时

忙等的过程,确实在等,但是也消耗了很多 cpu

1个notify起到两个xiaog

之所以咱们的代码,使用的是 PriorityQueue,而不是 PriorityBlockingQueue,其实就是因为要处理两个 wait 的地方使用阻塞版本的优先级队列,不方便实现这样的两处等待~~

mport java.util.PriorityQueue;// 通过这个类, 描述了一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 要有一个要执行的任务private Runnable runnable;// 还要有一个执行任务的时间private long time;// 此处的 delay 就是 schedule 方法传入的 "相对时间"public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}@Overridepublic int compareTo(MyTimerTask o) {// 这样的写法, 就是让队首元素是最小时间的值// 到底是谁 - 谁, 不要背!! 你可以试试!!return (int) (this.time - o.time);// 如果是想让队首元素是最大时间的值// return o.time - this.time;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}
}// 咱们自己搞的定时器
class MyTimer {// 使用一个数据结构, 保存所有要安排的任务.private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 使用这个对象作为锁对象.private Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized (locker) {queue.offer(new MyTimerTask(runnable, delay));locker.notify();}}// 搞个扫描线程.public MyTimer() {// 创建一个扫描线程Thread t = new Thread(() -> {// 扫描线程, 需要不停的扫描队首元素, 看是否是到达时间.while (true) {try {synchronized (locker) {// 不要使用 if 作为 wait 的判定条件, 应该使用 while// 使用 while 的目的是为了在 wait 被唤醒的时候, 再次确认一下条件.while (queue.isEmpty()) {// 使用 wait 进行等待.// 这里的 wait, 需要由另外的线程唤醒.// 添加了新的任务, 就应该唤醒.locker.wait();}MyTimerTask task = queue.peek();// 比较一下看当前的队首元素是否可以执行了.long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 当前时间已经达到了任务时间, 就可以执行任务了task.getRunnable().run();// 任务执行完了, 就可以从队列中删除了.queue.poll();} else {// 当前时间还没到任务时间, 暂时不执行任务.// 暂时先啥都不干, 等待下一轮的循环判定了.locker.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo26 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("2000");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("1000");}}, 1000);System.out.println("程序开始执行");}
}

 2.线程池

线程诞生的意义,是因为进程的创建/销毁, 太重量了(比较慢)有对比,才有伤害,和进程比,线程,是更快了,但是如果进一步提高创建销毁的频率, 线程的开销也不能忽视了!

两种典型的办法,进一步提高效率:

1.协程(轻量级线程)

相比于线程,把系统调度的过程,给省略了.(程序猿手工调度当下,一种比较流行的并发编程的手段. 但是在 Java 圈子里,协程还不够流行.

2.线程池

线程池最大的好处就是减少每次启动、销毁线程的损耗。
在使用第一个线程的时候,提前把 2345..线程创建好 (培养感情)
后续如果想使用新的线程,不必重新创建了,直接拿过来就能用!!!(此时创建线程的开销就被降低了)

2.1 线程池的使用 

把线程创建好,放在池子里,后续用的时候直接从池子里来取~~

为什么从池子取, 的效率比新创建线程,效率更高??? 

从池子取,这个动作,是纯粹用户态的操作.

创建新的线程,这个动作,则是需要 用户态 +内核态 相互配合,完成的操作

 出现的问题

很多时候 构造一个对象,希望有多种构造方式.
多种方式,就需要使用多个版本的构造方法来分别实现.
但是构造方法要求方法的名字必须是类名,不同的构造方法,就只能通过 重载 的方式来区分了.(重载 =>参数类型/个数 不同)

上面两个代码并没有构成重载,故编译失败。

工厂设计模式

解决方案:使用工厂设计模式

使用工厂设计模式,就能解决这个问题.
使用普通的方法,代替构造方法完成初始化工作.普通方法就可以使用方法的名字来区分了.也就不再收到重载的规则制约了
实践中, 一般单独搞一个类,给这个类搞一些静态方法, 由这样的静态方法负责构造出对象。

使用不同的方法名做出区分

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo27 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}

Executors 创建线程池的几种方式

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池. 
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer. 

上述这几个工厂方法生成的线程池,本质上都是对一个 类 进行的封装,ThreadPoolExecutor这个类,功能非常丰富,提供了很多参数,标准库上述的几个工厂方法,其实就是给这个类填写了不同的参数用来构造线程池了【Executors 本质上是 ThreadPoolExecutor 类的封装】

  • 不同的拒绝策略有不同的效果

使用线程池,需要设置线程的数目.数目设置多少合适??

ExecutorService service = Executors.newFixedThreadPool(4);

在接触到实际代码之前是无法确定的。

一个线程,执行的代码,主要有两类:
1.cpu 密集型: 代码里主要的逻辑是在进行 算术运算/逻辑判断

2.IO密集型: 代码里主要进行的是 Io操作,
假设一个线程的所有代码都是 cpu 密集型代码,这个时候,线程池的数量不应该超过 N(设置 N 就是极限了)设置的比 N 更大,这个时候,也无法提高效率了.(cpu 吃满了)此时更多的线程反而增加调度的开销.
假设一个线程的所有代码都是 Io密集的,这个时候不吃 CPU,此时设置的线程数,就可以是超过 N.较大的值一个核心可以通过调度的方式,来并发执行~

我们就可以知道:

代码不同, 线程池的线程数目设置就不同,无法知道一个代码,具体多少内容是 cpu 密集, 多少内容是Io密集

正确做法: 使用实验的方式,对程序进行性能测试,测试过程中尝试修改不同的线程池的线程数目,看哪种情况下,最符合要求

 2.2 线程池的实现

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPool {// 任务队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// 通过这个方法, 把任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {// 此处咱们的拒绝策略, 相当于是第五种策略了. 阻塞等待~~ (这是下策)queue.put(runnable);}public MyThreadPool(int n) {// 创建出 n 个线程, 负责执行上述队列中的任务.for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 让这个线程, 从队列中消费任务, 并进行执行.try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}});t.start();}}
}public class Demo28 {public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for (int i = 0; i < 1000; i++) {int id = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务: " + id);}});}}
}

主线:

线程概念 -> Thread 用法 ->线程安全问题 ->wait notify -> 线程案例

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

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

相关文章

Linux之线程管理

目录 第1关&#xff1a;创建线程 任务描述 相关知识 使用pthread_create函数创建线程 编程要求 答案&#xff1a; 第2关&#xff1a;线程挂起 任务描述 相关知识 使用pthread_join挂起线程 编程要求 答案&#xff1a; 第3关&#xff1a;线程终止 任务描述 相关知识 使用pthread…

18种WEB常见漏洞:揭秘网络安全的薄弱点

输入验证漏洞: 认证和会话管理漏洞: 安全配置错误: 其他漏洞: 防范措施: Web 应用程序是现代互联网的核心&#xff0c;但它们也容易受到各种安全漏洞的影响。了解常见的 Web 漏洞类型&#xff0c;对于开发人员、安全测试人员和普通用户都至关重要。以下将介绍 18 种常见的 …

MySQL—MySQL的存储引擎之InnoDB

MySQL—MySQL的存储引擎之InnoDB 存储引擎及种类 存储引擎说明MyISAM高速引擎&#xff0c;拥有较高的插入&#xff0c;查询速度&#xff0c;但不支持事务InnoDB5.5版本后MySQL的默认数据库存储引擎&#xff0c;支持事务和行级锁&#xff0c;比MyISAM处理速度稍慢ISAMMyISAM的…

Android Studio查看viewtree

前言&#xff1a;之前开发过程一直看的是手机上开发者选项中的显示布局边界&#xff0c;开关状态需要手动来回切换&#xff0c;今天偶然在Android Studio中弄出了布局树觉得挺方便的。

JPEG图像常用加密算法简介

JPEG图像加密算法 目前&#xff0c;JPEG图像加密算法可以分成异或加密、置乱加密和置乱与异或组合加密。下面对这三种加密方式进行阐述。 (1) 异或加密 文献[1]提出了一种基于异或加密的JPEG图像的RDH-EI方案。该算法通过对AC系数的ACA和图像的量化表进行流密码异或&#xf…

代码随想录训练营Day 33|Python|Leetcode|● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

理论基础 动态规划五步曲 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始…

vue3——笔记2(计算属性,类与样式绑定)

计算属性 在 Vue3 中&#xff0c;计算属性的用法和 Vue2 基本上是一样的&#xff0c;但是在性能上有了一些改进。Vue3 中计算属性是通过computed函数来创建的&#xff0c;计算属性的值会在相关依赖发生改变时自动更新。与 Vue2 相比&#xff0c;Vue3 的计算属性在一些场景下会…

某翻译平台翻译接口逆向之webpack学习

逆向网址 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw 逆向链接 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLyMv 逆向接口 aHR0cHM6Ly9kaWN0LnlvdWRhby5jb20vd2VidHJhbnNsYXRl 逆向过程 请求方式 POST 逆向参数 sign c168e4cb76169e90f82d28118dbd24d2 接口请求结果解密 过程分析 根据XHR…

大数据第七天

文章目录 吐槽一下这个是怎么需要真的这么大吗? 内核错误内核软死锁&#xff08;soft lockup&#xff09;我这个cpu很高吗?大模型都说了不超过80就行了 FinBi安装FinBI下载链接安装时间比较长 吐槽一下 dbeaver 查询hive 数据信息是真的慢&#xff0c;没有一点快的方式&…

【优秀AI项目】每日跟踪 OpenVoice ,AI快站,OpenVoice

持续更新好玩的开源AI项目或AI商业应用体验 一起来玩转AI&#xff01;&#xff01; 1 huggingface 国内镜像站&#xff1a;AI 快站 HUggingface被墙了&#xff0c;emmmmm 所以我之前玩模型的一大感觉就是 下载什么模型之类的太难受了&#xff01;服了 看到一个镜像站——…

在Visio中插入半圆状箭头

在 Microsoft Visio 中&#xff0c;你可以通过以下步骤来绘制一个带箭头的半圆&#xff1a; 1.打开 Visio&#xff1a;打开 Microsoft Visio 软件。 2.选择绘图类型&#xff1a;在 Visio 中&#xff0c;你可以选择使用“基本形状”或“箭头线”工具来绘制带箭头的半圆。 使用…

文件权限管理

文件权限管理 1. 权限对象 权限对象含义u属主&#xff0c;所有者g属组o其他人 2. 权限类型 权限类型含义值r读权限4w写权限2x执行权限1 3. 修改文件属主及属组 命令:chown(change own)更改文件或目录属主与属组名 3.1 修改文件属主与属组 只修改属主&#xff1a;chown $…

Open CASCADE学习|一个点的坐标变换

gp_Trsf 类是 Open CASCADE Technology (OCCT) 软件库中的一个核心类&#xff0c;用于表示和操作三维空间中的变换。以下是该类的一些关键成员和方法的介绍&#xff1a; 成员变量&#xff1a; scale: Standard_Real 类型&#xff0c;表示变换的缩放因子。 shape: gp_TrsfFor…

Android11 SystemUI clock plugin 插件入门

插件的编写 参照ExamplePlugin&#xff0c;需要系统签名。 需要先编译以下模块得到jar&#xff0c;引用在项目中。 m SystemUIPluginLibcom.android.systemui.permission.PLUGIN PluginManager.addPluginListener SystemUI 是如何发现 clock plugin 的&#xff1f; Syste…

FDY10蓄电池容量检测仪

FDY10-H说明书2013 08.pdf (book118.com)https://max.book118.com/html/2017/0510/105769526.shtm FDY10用户手册 - 百度文库 (baidu.com)https://wenku.baidu.com/view/22e7fe672d3f5727a5e9856a561252d380eb20ac?aggId28d62908f12d2af90242e62a&frcatalogMain_graph_v10…

ThingsBoard处理设备上报的属性并转换为可读属性

一、前言 二、案例 1、AI生成JSON数据体 2、将json数据体直接通过遥测topic发送查看效果 3、可查看目前整个数据都在一起 ​编辑 4、配置附规则链路 5、对msg的消息值&#xff0c;进行数据的转换&#xff0c;并从新进行赋值。 6、规则链路关联关系 7、再次通过MQTT发送遥…

WebGIS

文章目录 GIS的全名是Geographic Information System&#xff0c;中文全名是地理信息系统。 它是在计算机硬、软件系统支持下&#xff0c;对整个或部分地球表层&#xff08;包括大气层&#xff09;空间中的有关地理分布数据进行采集、储存、管理、运算、分析、显示和描述的技术…

详解23种设计模式——单例模式

单例模式 | CoderMast编程桅杆单例模式 单例模式是最常用的设计模式之一&#xff0c;他可以保证在整个应用中&#xff0c;某个类只存在一个实例化对象&#xff0c;即全局使用到该类的只有一个对象&#xff0c;这种模式在需要限制某些类的实例数量时非常有用&#xff0c;通常全局…

【GitHub】如何在github上提交PR(Pull Request) + 多个pr同时提交、互不干扰

【GitHub】如何在github上提交PR(Pull Request 写在最前面1. 准备工作1.1 注册 GitHub 账号1.2 了解 Git 基础1.3 找到一个项目 2. 创建你的 PR2.1 Fork 和克隆仓库2.2 创建一个新的分支2.3 进行更改2.4 推送更改到 GitHub2.5 创建 Pull Request 3. 优化你的 PR3.1 保持提交清晰…

JetBot手势识别实验

实验简介 本实验目的在JetBot智能小车实现手势识别功能&#xff0c;使用板卡为Jetson Nano。通过小车摄像头&#xff0c;识别五个不同的手势&#xff0c;实现小车的运动及灯光控制。 1.数据采集 连接小车板卡的Jupyterlab环境&#xff0c;运行以下代码块&#xff0c;配置数据…