【多线程】定时器 | 线程池 | 实现MyTimer | 实现MyThreadPoll | 工厂模式 | 构造方法 | 参数种类

文章目录

  • 定时器&线程池
    • 一、定时器
        • 1.标准库中的定时器
        • 2.实现定时器
    • 二、线程池
        • 1.线程池的概念
            • 线程池:
        • 2.标准库当中的线程池
          • 工厂模式
          • Executors 创建线程池
            • 1.自适应线程池
            • 2.固定数量线程池
            • 3.只有单个线程的线程池
            • 4.设定延迟时间后执行命令的线程池
          • ThreadPoolExecutor 类
            • 1.注册任务
            • 2.构造
            • 使用线程池,需要设置线程的数目
        • 3.实现线程池

定时器&线程池


一、定时器

  • 类似一个"闹钟",约定一个时间,时间到达之后,执行某个代码逻辑
  • 在进行网络通信中很常见

客户端在发出请求后,就要等待响应。但是如果服务器迟迟没有响应,客户端不能无限的等下去,需要有一个最大的期限,等时间到了之后再次进行判断。而“等待的最大时间”就可以通过定时器的方式来实现。

1.标准库中的定时器
  • import java.util.Timer 。 Timer这个类是在util里的
    public static void main(String[] args) {Timer timer = new Timer();//给定时器安排了一个任务,预订在2秒后执行。timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器");}},2000);System.out.println("程序启动");}程序启动
执行定时器//两秒后
  • schedule()方法的第一个参数:使用匿名内部类,创建一个TimerTask()实例,重写当中的run方法,通过run方法来描述任务的详细情况

在这里插入图片描述

TimerTask类本身就实现了Runnable接口。从而重写run方法

  • schedule方法的第二个参数:填写的时间,表示当前这个任务以此时此刻为基准,往后推X时间后再执行该任务。

    当主线程在执行schedule方法的时候,就会把任务放进timer对象中。同时,timer当中,存在一个扫描线程。一旦时间到了,扫描线程就会执行刚才安排的任务。换句话说,timer当中的任务,是有当中的扫描线程来执行的。当任务结束时,扫描线程并未结束,还在等待执行后续可能安排的任务

    public static void main(String[] args) {Timer timer = new Timer();//给定时器安排了一个任务,预订在2秒后执行。timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器3");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器1");}},1000);System.out.println("程序启动");}程序启动
执行定时器1
执行定时器2
执行定时器3
2.实现定时器

要有一个扫描线程,扫描任务是否到达时间执行

要有一个优先级对列来保存任务:o(1),优先取时间最小的任务执行。

创建一个类,通过类的对象来描述任务(任务内容、任务时间)

class MyTimerTask implements Comparable<MyTimerTask> {//描述一个任务private Runnable runnable;//要执行的任务private long time;MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;//当前的时间戳+要延迟的时间}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}@Overridepublic int compareTo(MyTimerTask o) {//保证队首的任务是是最小的时间return (int) (this.time - o.time);}
}
  • 如果队列为空了,就需要 调用wait来进行阻塞,同时需要搭配synchronized来使用。因为wait的操作有三个:1在前提有锁的情况下,释放锁。2.等待通知。3.通知到来之后,进行唤醒,同时重新拿到锁。
  • 对应的,也需要在调用schedule方法添加任务时,对之前因为队列为空而等待的wait进行唤醒(notify)

同时:由于schedule方法和扫描线程都会操作队列。存在线程安全问题。因此要加锁。

//自己实现的定时器
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();//唤醒空对列的wait}}public MyTimer() {//创建一个扫描线程Thread t = new Thread(() -> {while (true) {//不停扫描队首元素try {synchronized (locker) {while (queue.isEmpty()) {//使用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(); }
}

​ 由于扫描线程是while(true)循环,在队列不为空的情况下,不会进入等待。会一直持续循环,直到当前时间到达设定的时间为止。在此期间并没有进行任何操作,只是不断的对表忙等,但是消耗了很多cpu资源。所以在第一次判断时间时,在else中,当任务时间还没到的时候,进行wait阻塞,此时线程不会在CPU上调度,避免了忙等。

  • 同时:如果schedule方法添加了一个比当前待任务要早执行的任务时,schedule方法内部的notify就会唤醒这个带参数的wait,让循环再执行一次,重新拿到新的队首元素,跟新wait的时间。

也就是说:当队列为空时进行阻塞等待,调用一次schedule方法,其中的notify可以唤醒wait。而在等待要执行的任务时,调用schedule方法存入一个任务。其中的notify会唤醒带参数的wait,再次循环,重新获取队首任务,更新等待时间。

二、线程池

1.线程池的概念

​ 由于进程创建/销毁,太重量了(比较慢)才引进了线程的概念,但是如果进一步提高创建销毁的频率,线程的开销就不容忽视。

有两种办法来提高线程的效率:

  • 1.协程(轻量级线程)

    线程省略的是资源创建的过程。先比与线程,协程省略了系统调度的过程。(程序员手动调度)

    Java虽然标准库中没有协程,但是一些第三方库实现了协程。

  • 2.线程池 同样可以提高线程的效率,同时避免了全面的修改。减少了每次创建、销毁线程的损耗

线程池:

​ 在使用第一个线程的时候,提前把后续多个线程创建好,放进池中。后续如果想使用多个线程,不必重新创建,直接从池中拿过来用,降低创建线程的开销。

而从池中这个操作,是用户态的操作。而创建一个线程,则是用户态+内核态 相互配合来完成的操作。

​ 如果一段程序在系统内核中执行,就被称为内核态。否则就是用户态。而一个操作系统则是由操作系统内核和配套的应用程序构成。创建一个线程,就会需要调用系统API,进入到内核中,按照内核态的方式来完成一系列操作。操作系统内核是要对所有的进程提供服务的,当要创建一个线程的时候,内核难免会被干扰做其他事情,不可控,不可预期。而用户态的操作不涉及系统内核,可控可预期。因此线程池的操作要比创建线程更高效。

2.标准库当中的线程池
        ExecutorService service = Executors.newCachedThreadPool()//可执行的服务
  • 线程池对象不是直接new出来的,而是通过专门的方法,返回了一个线程池对象。这种写法叫做工厂模式
工厂模式

工厂模式是一种常见的设计模式

​ 通常在使用new关键字创建对象时,会触发类的构造方法来实例对象。但是构造方法存在一定的局限性,工厂模式就可以解决构造方法的不足

class Point{public Point(double x,double y){//通过笛卡尔坐标系构造点}public Point(double r,double a){//使用极坐标构造点}
}
  • 此时,在这个类中,两个构造方法采用的是两种截然不同的方式,但是构造方法的方法名必须是类名,不同的构造方法只能通过重载来进行区分(重载要求参数类型/个数不同)。但是此时,两个构造方法的参数列表相同,没有完成重载,编译失败。
  • 而采用工程模式,使用普通的方法,代替构造方法来完成初始化工作,普通方法就可以使用不同方法名来进行区分。
class PointFactory{public static Point makePointByXY(double x,double y){Point p  =new Point();p.setX(x){}p.setY(y){}return p;}public static Point makePointByRA(double x,double y){return p;}
}Point p = PointFactory.makePointByXY(15,20);
//就类似于线程池的创建
ExecutorService service = Executors.newCachedThreadPool()//工厂类   //工厂方法
  • 通过方法名来完成区分
Executors 创建线程池
1.自适应线程池

​ newCachedThreadPool,可以动态适应。随着往线程池中添加任务,这个线程池中的线程会根据需要自动被创建出来,并且使用后不会立即销毁,会在池中保留一定的时间,以备后续再次使用

        ExecutorService service = Executors.newCachedThreadPool();//可以动态适应//cached:缓存,用过之后不着急释放,先保留,
2.固定数量线程池
		 ExecutorService service1 = Executors.newFixedThreadPool(10);//固定的
3.只有单个线程的线程池
		ExecutorService service2 = Executors.newSingleThreadExecutor();
4.设定延迟时间后执行命令的线程池
        ExecutorService service3 = Executors.newScheduledThreadPool(5); 
//类似于定时器,但是不在是一个扫面线程在执行任务,而是变成了多个线程来执行任务。
ThreadPoolExecutor 类

​ Executors 本质上是 ThreadPoolExecutor 类的封装。ThreadPoolExecutor 类的功能非常丰富,提供了很多参数。标准库当中的几个工厂方法,其实就是给这个类填写了不同的参数来构造线程池

ThreadPoolExecutor 类的核心方法有两个:

1.注册任务
        ExecutorService service1 = Executors.newFixedThreadPool(10);service1.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});
2.构造

ThreadPoolExecutor当中的构造参数有很多(面试题)

在这里插入图片描述

JUC这个包就是和并发编程相关的内容(多线程)

ThreadPoolExecutor的构造方法有四个版本,其中最后一个版本参数最多,可以涵盖其余方法的参数

在这里插入图片描述

​ int corePoolSize(核心线程数)int maximumPoolSize(最大线程数), 描述线程的数目

这个线程池中,线程的数目是可以动态变化的,线程数变化的范围就是 [ corePoolSize, maximumPoolSize ]

核心线程数(正式员工数量) ;最大线程数(正式员工数量 + 实习生的数量)

实习生不允许摸鱼,活多了招人,少了裁人。但是不会动正式员工

在满足效率的同时,又可以避免过多的系统开销

​ BlockingQueue 阻塞队列:可以根据需要灵活选择队列,需要有优先级,设置PriorityBlockingQueue

如果不需要优先级,并且任务数量相对恒定,使用ArrayBlockingQueue。如果任务数量变动较大,

使用LinkedBlockingQueue.

​ 使用工厂类来创建线程,主要是为了在创建的过程中,对线程的属性做一些设置。如果手动创建线程,就需要手动设置这些属性,所以用工厂方法进行封装。

​ RejectedExecutionHandler handler 线程池的拒绝策略,一个线程池的容量是有限的,达到上限后,采用不同的拒绝策略会有不同的效果。(4种)

在这里插入图片描述

使用线程池,需要设置线程的数目

设置多少线程合适?

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

一个线程,要执行的代码主要有两大类:

1.CPU密集型:代码中主要的逻辑是进行算数运算/逻辑判断

2.IO密集型:代码里主要进行IO操作。(网络通信、写硬盘、读硬盘)

​ 假设一个线程的所有代码都是CPU密集型代码,线程池中的线程数量不应该超过N(CPU核心数),设置的比N大,cpu吃满了,无法提高效率,此时添加更多的线程反而增加调度的开销。

​ 假设一个线程的所有代码都是IO密集型的,这个时候不吃CPU,此时设置的线程数,就可以超过N.一个核心可以通过调度的方式,来并发执行。

​ 代码不同,线程池的线程数目设置就不同。正确的设置方法:使用实验的方式,对程序进行性能测试。在测试的过程中,尝试修改不同的线程池的线程数目。看哪种情况最符合需求。

3.实现线程池
class MyThreadPool{//任务队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);//通过submit方法,将任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {//次处的拒绝策略相当于第5种策略:阻塞等待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 MakeMyThreadPoll {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);//防止匿名内部类的变量捕获//此时捕获的是id,id没有人进行修改。每次循环都创建了新的id}});}}

点击移步博客主页,欢迎光临~

偷cyk的图

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

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

相关文章

BNB链融合

BNB Chain融合 BNB Chain目前有BNB智能链&#xff08;BSC&#xff09;&#xff0c;BNB信标链 BNB信标链&#xff1a;用作质押和投票的治理层&#xff0c;采用BEP-2代币标准BNB智能链(BSC)&#xff1a;用作EVM兼容层&#xff0c;提供DApp、DeFi服务、共识层、多链支持和其他Web3…

阿里云服务器上配置Docker 以及常用命令讲解

目录 一、认识docer二、在阿里云服务器上配置Docker三、底层原理4、常用命令&#xff08;1&#xff09;Docker中常见镜像命令&#xff08;2&#xff09;Docker中常见容器命令&#xff08;3&#xff09;日志查看命令&#xff08;4&#xff09;进入容器的命令与拷贝命令 一、认识…

【目标检测】Focal Loss

Focal Loss用来解决正负样本不平衡问题&#xff0c;并提升训练过程对困难样本的关注。 在一阶段目标检测算法中&#xff0c;以YOLO v3为例&#xff0c;计算置信度损失&#xff08;图中第3、4项&#xff09;时有目标的点少&#xff0c;无目标的点多&#xff0c;两者可能相差百倍…

【1524】java投票管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 投票管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

Rust入门-所有权与借用

一、为什么、是什么、怎么用 1、为什么Rust要提出一个所有权和借用的概念 所有的程序都必须和计算机内存打交道&#xff0c;如何从内存中申请空间来存放程序的运行内容&#xff0c;如何在不需要的时候释放这些空间&#xff0c;成为所有编程语言设计的难点之一。 主要分为三种…

java新冠病毒密接者跟踪系统(springboot+mysql源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的新冠病毒密接者跟踪系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 新冠病毒密接者跟…

Java垃圾回收1

1.对象什么时候可以被垃圾器回收 1.垃圾回收的概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c; 在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)。 有了垃圾…

2、MATLAB入门常用命令

一、退出和中断 exit和quit&#xff1a;结束MATLAB会话。程序完成&#xff0c;如果没有明确保存&#xff0c;则变量中的数据丢失。 Ctrl c&#xff1a;中断一个MATLAB任务。例如&#xff0c;当MATLAB正在计算或打印时&#xff0c;中断一个任务&#xff0c;但会话并没有结束。…

麒麟服务器操作系统自动化安装应答文件制作

原文链接&#xff1a;麒麟服务器操作系统自动化安装应答文件制作 Hello&#xff0c;大家好啊&#xff01;今天我们将探讨如何为麒麟服务器操作系统制作自动化安装应答文件。在部署大量服务器时&#xff0c;自动化安装是提高效率和确保安装一致性的关键技术。通过使用应答文件&a…

云原生Kubernetes: K8S 1.29版本 部署Kuboard

目录 一、实验 1.环境 2.K8S 1.29版本 部署Kuboard (第一种方式) 3.K8S 1.29版本 部署Kuboard (第二种方式) 4.K8S 1.29版本 使用Kuboard 二、问题 1.docker如何在node节点间移动镜像 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构版本IP备注ma…

太阳能路灯光伏板的朝向设计问题

题目&#xff1a;太阳能路灯光伏板的朝向设计问题 难度对标几乎每一年的国赛A题。 QQ群&#xff1a;592697532 公众号&#xff1a;川川菜鸟 文章目录 背景问题问题一问题二问题三 题目解读相关公式&#xff08;必备&#xff09;太阳辐射的计算光伏板接收的辐射光学效率大 气透…

Spring Cloud Gateway详细介绍以及实现动态路由

一. 简介 Spring Cloud Gateway This project provides a libraries for building an API Gateway on top of Spring WebFlux or Spring WebMVC. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to …

C++的线程

#include<iostream> #include<thread> #include<unistd.h> using namespace std; void myrun() {while(true){cout<<"I am a thread"<<endl;sleep(1);} } int main() {thread t(myrun);t.join();return 0; } 如果不添加-lpthread就会报…

基于ChatGPT打造安全脚本工具流程

前言 以前想要打造一款自己的工具&#xff0c;想法挺好实际上是难以实现&#xff0c;第一不懂代码的构造&#xff0c;只有一些工具脚本构造思路&#xff0c;第二总是像重复造轮子这种繁琐枯燥工作&#xff0c;抄抄改改搞不清楚逻辑&#xff0c;想打造一款符合自己工作的自定义的…

Day 25 组合(优化)216.组合总和III 17.电话号码的字母组合

组合&#xff08;优化&#xff09; 先给出组合问题的回溯部分代码&#xff1a; vector<vector<int>> result; // 存放符合条件结果的集合vector<int> path; // 用来存放符合条件结果void backtracking(int n, int k, int startIndex) {if (path.size() k) …

【opencv】dnn示例-person_reid.cpp 人员识别(ReID,Re-Identification)系统

ReID(Re-Identification&#xff0c;即对摄像机视野外的人进行再识别) 0030_c1_f0056923.jpg 0042_c5_f0068994.jpg 0056_c8_f0017063.jpg 以上为输出结果&#xff1a;result文件夹下 galleryLIst.txt queryList.txt 模型下载&#xff1a; https://github.com/ReID-Team/ReID_e…

OpenHarmony网络通信-socket-io

简介 socket.io是一个在客户端和服务器之间实现低延迟、双向和基于事件的通信的库。建立在 WebSocket 协议之上&#xff0c;并提供额外的保证&#xff0c;例如回退到 HTTP 长轮询或自动重新连接。 效果展示 下载安装 ohpm install ohos/socketio OpenHarmony ohpm 环境配置等更…

VulnHub靶机 DC-5 打靶 渗透测试详情过程

VulnHub靶机 DC-5 打靶 详细渗透测试过程 目录 VulnHub靶机 DC-5 打靶 详细渗透测试过程一、将靶机导入到虚拟机当中二、渗透流程主机发现端口扫描目录爆破文件包含getshell反弹shell提权 一、将靶机导入到虚拟机当中 靶机地址&#xff1a; https://download.vulnhub.com/dc/…

【云计算】云计算八股与云开发核心技术(虚拟化、分布式、容器化)

【云计算】云计算八股与云开发核心技术&#xff08;虚拟化、分布式、容器化&#xff09; 文章目录 一、什么是云计算&#xff1f;1、云计算的架构&#xff08;基础设施&#xff0c;平台&#xff0c;软件&#xff09;2、云计算的发展 二、如何做云计算开发&#xff1f;云计算的核…

量子时代加密安全与区块链应用的未来

量子时代加密安全与区块链应用的未来 现代密码学仍然是一门相对年轻的学科&#xff0c;但其历史却显示了一种重要的模式。大多数的发展都是基于几年甚至几十年前的研究。而这种缓慢的发展速度也是有原因的&#xff0c;就像药物和疫苗在进入市场之前需要经过多年的严格测试一样&…