9.多线程之定时器与线程池

news2024/9/27 9:19:29

定时器与线程池

文章目录

  • 定时器与线程池
  • 1. 定时器
    • 1.1 定时器的工作原理
    • 1.2 定时器的使用
    • 2.3 实现定时器
  • 2. 线程池
    • 2.1 线程池存的优点
    • 2.2 线程池的使用
    • 2.3 线程池的原理
      • 2.3.1 工厂模式
      • 2.3.2 ThreadPoolExecutor类
    • 2.4 实现线程池

1. 定时器

    定时器也是软件开发中的一个重要组件,我们可以将一个任务交给定时器,约定好时间到了定时器就执行该任务。
    比如,当客户端发出请求后,会等待服务器的响应,但是由于网络环境的复杂性,如果吃吃没有得到响应,也不会一直等待下去(不现实),此时就会设定一个最大等待时间,这里的这个最大等待时间,就可以使用定时器的方式来实现,当时间到达最大等待时间后,就放弃等待。

1.1 定时器的工作原理

    在标准库中提供了定时器Timer类,Timer 类的核心方法为 schedule(),
schedule() 包含两个参数,第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。

    定时器会将传入的任务和与其相对应的等待时间存放到带有阻塞的优先级队列(线程安全)中,每次只需要维护时间最短的任务,时间没到时就可以进入阻塞,当时间间隔最短的任务到了执行时间,就对其进行出队操作,并执行任务里面的内容。

1.2 定时器的使用

  public static void main(String[] args) {
        System.out.println("程序启动");
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时器任务1");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时器任务2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时器任务3");
            }
        },1000);
    }

执行结果~~

程序启动
执行定时器任务3
执行定时器任务2
执行定时器任务1

    我们可以看到,定时器按照我们指定的任务和时间进行工作了,但是任务执行完毕,线程并不会结束,而是会进入阻塞,等待下一个任务的到来。

2.3 实现定时器

    定时器是通过优先级队列来维护待执行的任务及其对应的时间,并且我们要实现compareTo接口。


class Task implements Comparable<MyTask>{
    private Runnable runnable;// 任务 - 交给线程执行
    private long time;// 等待时间
 
    public Task(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取任务的时间
    public long getTime() {
        return time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }
 
    @Override
    public int compareTo(Task o) { // 重写compare接口
        return (int)(this.time - o.time);
    }
}

    定时器中有一个扫描线程扫描优先级队列中(优先级最高的)的任务是否到了执行时间。
    不断将队(优先级最高的)首元素取出,判断是否到达时间,然候根据情况选择是将任务放回还是执行,这个需要一个循环去不停的执行。

    但是如果任务的执行时间和当前时间相差很多,不断的取出、判断、放回 会额外占用很多cpu资源(线程忙等),所以我们对其考虑进行一个睡眠操作,睡眠的时间就是 – 任务要执行的时间减去当前的时间,但是,如果有新的任务传了进来,并且时间比当前的最短时间要短就可能出现新任务没有被执行的情况,所以,上面的睡眠操作是不可取的,而是应该使用wait,在wait中设置最大的等待时间,当有新的任务传进来时将wait唤醒,然后重新判断,具体实现如下:

class MyTimer {
    //扫描线程
    private Thread t = null;
 
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
 
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {
                try {
                    synchronized(this) {
                        Task myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if(curTime < myTask.getTime()) {
                            queue.put(myTask);
                            this.wait(myTask.getTime() - curTime);
                        } else {
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
 
    public void schedule(Runnable runnable,long after) {
        //时间戳需要进行换算
        Task myTask = new Task(runnable,System.currentTimeMillis() + after);
        queue.put(myTask);
        synchronized(this) {
            this.notify();
        }
    }
}

    一定要注意加锁的范围,锁一定要包括扫描线程while里面的全部内容,因为如果扫描线程计算完需要等待的时间之后wait之前,扫描线程被切走,此时有一个新任务传了进来,执行了notify之后扫描线程才开始进行工作,那么扫描线程就没有扫描到新的任务,如果新的任务的时间更短,那么新的任务就没有被执行。

完整代码


class Task implements Comparable<Task>{
    private Runnable runnable;
 
    private long time;
 
    public Task(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取任务的时间
    public long getTime() {
        return time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }
 
    @Override
    public int compareTo(Task o) {
        return (int)(this.time - o.time);
    }
}
 
class MyTimer {
    //扫描线程
    private Thread t = null;
 
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
 
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {
                try {
                    synchronized(this) {
                        Task myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if(curTime < myTask.getTime()) {
                            queue.put(myTask);
                            this.wait(myTask.getTime() - curTime);
                        } else {
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
 
    public void schedule(Runnable runnable,long after) {
        //时间戳需要进行换算
        Task myTask = new Task(runnable,System.currentTimeMillis() + after);
        queue.put(myTask);
        synchronized(this) {
            this.notify();
        }
    }
}

2. 线程池

2.1 线程池存的优点

    线程是系统调度的最小单位,由于进程的开销太大,就引入了线程,在Java中更喜欢使用多线程。除了线程还有一种“协程”也可以进一步提高性能,但是有于标准库并没有很好的支持,在Java中使用的并不算多。
    但由于对性能的进一步追求,此时就引入了线程池的技术,池化技术是一种非常重要的思想,使用非常广泛,比如,线程池、内存池、常量池、连接池等。
    我们可以提前(一次性)创建一些线程,加入线程池中,在需要使用线程时,直接去线程池中取,不用时也不着急释放。这样由于是一次性创建(内核态+ 用户态),之后从线程池中拿取(户态操作),就会提降低程序的开销。此时申请和释放的”度“可以由程序员去自己设计,而不用全部听从系统内核的调度,使得程序更加的可控。

2.2 线程池的使用

常见的线程池创建有四种:

//可以设置线程数量
ExecutorService pool = Executors.newFixedThreadPool(10);
//根据任务的数量 去动态变化线程的数量
ExecutorService pool = Executors.newCachedThreadPool();
//只有一个线程
ExecutorService pool = Executors.newSingleThreadExecutor();
//类似定时器,让任务延时进行
ExecutorService pool = Executors.newScheduledThreadPool(10);

    使用的方法很简单,就是调用里面的submit方法,在里面传一个任务就可以了,如下:创建1000个任务让线程池执行

public static void main(String[] args) {
    //	设置线程数量
    ExecutorService pool = Executors.newFixedThreadPool(10);
    for(int i = 0;i < 1000;i++) {
        int n = i;
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello" + n);
            }
        });
    }
}

2.3 线程池的原理

2.3.1 工厂模式

    线程池的创建时没有使用new的,而是调用了一个方法,而真正的new操作在方法里面进行,这样的设计模式叫做工厂模式。

    工厂模式作用是什么呢?在Java中,重载的规则是在 方法名(可以省略,因为重载主要就是需要方法名相同)、参数个数、参数类型 其中至少有一项不同,否则就无法达成重载。
    那么如果有两种构造方法,他们想要构成重载,但是又达不到重载的条件,此时,就可以使用工厂模式,去根据不同的需求去构造。

    举个例子,假如有一个类,它的用途是构造出一个坐标系上的点,构造这样的点可以传入x、y坐标,也可以传入距原点的半径长和角度大小r、a(极坐标),这四个参数都需要double类型,数量相同,方法名也相同,无法达成重载,此时就可以使用工厂模式去创建

Point point1 = newXYPoint(1,1);
Point point2 = newRAPoint(1,1);

2.3.2 ThreadPoolExecutor类

    线程池的四种构造方法都是对ThreadPoolExecutor的封装,我们只需要了解ThreaPoolExecutoe类即可。

    ThreadPoolExecutor类有7个参数。

  1. int corePoolSize

    • 核心线程数,也就是线程池中固定的线程数量
  2. int maximumPoolSize

    • 最大线程数,是线程池中可以包含的最大线程数量

    最大线程数和核心线程数的差值属于临时线程,也就是可以允许被回收掉的线程,比如当前任务量很多,那么就可以多创建几个临时线程去执行任务,当任务量比较少的时候,这些临时线程没有事情干就可以被回收掉。

  1. long keepAliveTime 和 TimeUnit unit(时间单位:s、ms、分钟…)

    • 这两个参数描述了临时线程可以最长的“闲置”时间,如果临时线程在一定的时间内没有工作,那么此时就会被回收掉。
  2. BlockingQueue workQueue

    • 线程池的任务队列,用一个阻塞队列来 接收、取出 任务。
  3. ThreadFactory threadFactory

    • 线程池的工厂方法,用于创建线程。
  4. RejectedExecutionHandler handler

    • 线程池的拒绝策略。

    标准库中提供了四种线程池的拒绝策略,如下:
在这里插入图片描述

  1. AbortPolicy - 队列满了,触发异常(摆烂了 - 罢工!!!)
  2. CallerRunsPolicy - 队列满了,就将该任务交还给加入线程执行(谁给我的谁执行)
  3. DiscardOldestPolicy - 队列满了,丢弃最早的任务,将该任务加入队列
  4. DiscardPolicy - 队列满了,丢弃最新任务,将该任务加入队列。

2.4 实现线程池

    简易线程池的实现:

class MyPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    public MyPool(int n) {
        for(int i = 0;i < n;i++) {
            Thread t = new Thread(() -> {
                while(true) {
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class PoolDemo2 {
    public static void main(String[] args) {
        MyPool myPool = new MyPool(10);
        for(int i = 0;i< 1000;i++) {
            int n = i;
            myPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + n);
                }
            });
        }
    }
}

    如果本篇文章对你有帮助,请点赞、评论、转发,你的支持是我创作的动力。

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

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

相关文章

【GEE】Google Earth Engine(GEE)注册详细教程无需教育邮箱

这个专栏真的是纠结了很久&#xff0c;不知道到底要不要分享自己在学习GEE的时候的一些经验和代码。因为本人在日常中使用Python和ENVI多点&#xff0c;虽然GEE也会用但不至于频繁使用&#xff0c;同时针对GEE其实官网给出了很多接口的使用方法&#xff0c;国内外也有很多人分享…

编译正点原子LINUXB报错make: arm-linux-gnueabihf-gcc:命令未找到

编译正点原子LINUXB报错make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到 1.报错内容2.解决办法3./bin/sh: 1: lzop: not found4.编译成功 1.报错内容 make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到CHK include/config/kernel.releaseCHK include/genera…

全面提升企业管理效率,助力企业持续增长——三叠云进销存解决方案

企业在生产经营过程中&#xff0c;高效的进销存管理是企业成功的关键因素之一。它不仅能够优化企业的资金流动&#xff0c;避免库存积压和断货现象&#xff0c;提高客户满意度&#xff0c;而且可以优化供应链&#xff0c;减少运营成本&#xff0c;提高生产和物流效率。三叠云正…

C语言 数据的存储2

如图所示代码&#xff1a; 代码运行结果为&#xff1a; 这是什么原因呢&#xff1f; 解析: 因为unsigned int是无符号整形&#xff0c;而我们的for循环的条件是 所以我们会一直循环下去&#xff0c;至于我们的结果为什么会是 一个unsigned int类型是4个字节&#xff0c;38位&am…

论文范文:论基于架构的软件设计方法及应用

注意:范文只适用于帮助大家打开写作思路,并不能作为素材直接用于平时练习、考试中。考试中直接使用范文的素材,会有被认定为雷同卷的风险。 摘要: 2022年4月,本人所在单位计划研发生态集装箱管理控制平台项目。该平台主要用于与现有公司生态集装箱产品做对接,达到远程控制…

【AI视野·今日Robot 机器人论文速览 第六十期】Mon, 23 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Mon, 23 Oct 2023 Totally 26 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers A Review of Prospects and Opportunities in Disassembly with Human-Robot Collaboration Authors Meng Lun Lee, Xiao Lian…

vscode开启emmet语法

需要在setting.json中添加配置 首先进入设置&#xff0c;然后点击右上角 Vue项目添加如下配置 "emmet.syntaxProfiles": { "vue-html": "html", "vue": "html" },React项目添加如下配置 "emmet.includeLanguages&quo…

Google搜索中,搜索关键词的技巧

最近在b站看到一个关于搜索的视频&#xff0c;感觉比较有用&#xff0c;先mark下来 1、巧用限定关键词符号""&#xff0c;也就是用英文的双引号&#xff0c;去搜索包含搜索词及这个搜索词字序的搜索结果。 如搜索 "菊花茶"&#xff0c;这个有用的点在于…

OpenCV官方教程中文版 —— Hough 圆环变换

OpenCV官方教程中文版 —— Hough 圆环变换 前言Hough 圆环变换 前言 目标 • 学习使用霍夫变换在图像中找圆形&#xff08;环&#xff09; • 学习函数&#xff1a;cv2.HoughCircles() Hough 圆环变换 opencv_logo.png&#xff1a; # -*- coding: utf-8 -*- import cv2 …

生态系统服务(InVEST模型)的人类活动、重大工程生态成效评估、论文写作等具体应用

以InVEST模型结合实际项目进行由浅入深的实战技术培训&#xff0c;Ai尚研修针对11期InVEST模型实践技术会议参会学者的特点及需求进行分析&#xff0c;融合课程体系&#xff0c;对接工作实际项目及论文写作&#xff0c;解决参会者关注的重点及实际项目过程问题&#xff0c;课程…

C++新经典 | 记录在最后的高阶知识点

目录 一、函数调用运算符与function类模板 1.函数调用运算符 &#xff08;1&#xff09;函数类型 &#xff08;2&#xff09;可调用对象 2.function类模板 二、万能引用类型 1.万能引用 2.万能引用资格的剥夺与辨认 &#xff08;1&#xff09;const修饰词 &#xff0…

人工智能基础_机器学习011_梯度下降概念_梯度下降步骤_函数与导函数求解最优解---人工智能工作笔记0051

然后我们来看一下梯度下降,这里先看一个叫 无约束最优化问题,,值得是从一个问题的所有可能的备选方案中选最优的方案, 我们的知道,我们的正态分布这里,正规的一个正态分布,还有我们的正规方程,他的这个x,是正规的,比如上面画的这个曲线,他的这个x,就是大于0的对吧,而现实生活…

Java反射调用ashx

这篇文章卡了大概一周&#xff0c;一个是没时间&#xff0c;只能带娃加锻炼间隙挤点时间&#xff0c;一个是碰到了问题卡住了。本篇实现反射调用ashx实现类的基础结构。 首先申明ashx的接口&#xff0c;所有的ashx实现类继承实现该接口的基类 package appcode; import java.i…

dbeaver查看表,解决证书报错current license is non-compliant for [jdbc]

http://localhost:9200/_license { “license” : { “status” : “active”, “uid” : “b91ae0e0-b04d-4e20-8730-cf0bca7b2035”, “type” : “basic”, “issue_date” : “2023-02-22T14:33:27.648Z”, “issue_date_in_millis” : 1677076407648, “max_nodes” : 10…

Leetcode 43. 字符串相乘 中等

题目 - 点击直达 1. 43. 字符串相乘 中等1. 题目详情1. 原题链接2. 题目要求3. 基础框架 2. 思路一 做加法1. 思路分析2. 时间复杂度3. 代码实现 3. 思路二 做乘法1. 思路分析2. 时间复杂度3. 代码实现 1. 43. 字符串相乘 中等 1. 题目详情 给定两个以字符串形式表示的非负整…

react中的useState和useImmer的用法

文章目录 一、useState1. 更新基本类型数据2. 更新对象3. 更新嵌套对象4. 更新数组5.更新数组对象 二、Immer1. 什么是Immer2. 使用use-immer更新嵌套对象3. 使用useImmer更新数组内部的对象 一、useState react中文官网教程 1. 更新基本类型数据 在函数式组件中&#xff0c…

【多线程相关其二】进程与线程

进程vs线程 进程&#xff08;process&#xff09;指的是正在运行的程序的实例&#xff0c;即an instance of a computer that is being executed。用拆字法理解就是&#xff1a;进行中的程序。程序是一个没有生命的实体&#xff0c;只有处理器执行它的时候才能成为一个活动的实…

macOS 创建Flutter项目

参考在 macOS 上安装和配置 Flutter 开发环境 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 这个文档&#xff0c;配置好flutter的环境 编辑器可以选择vscode或者IDEA。 我这里以IDEA为例 打开 IDE 并选中 New Flutter Project。 选择 Flutter&#xff0c;验证 F…

长图切图怎么切

用PS的切片工具 切片工具——基于参考线的切片——ctrl&#xff0b;shift&#xff0b;s 过长的图片怎么切 ctrl&#xff0b;alt&#xff0b;i 查看图片的长宽看图片的长宽来切成两个板块&#xff08;尽量中间切成两半&#xff09;用选区工具选中下半部分的区域——在选完时不…

电脑系统d3dcompiler_47.dll丢失问题,多种详细解决方法推荐

d3dcompiler_47.dll是Direct3D编译器组件的一部分&#xff0c;它是Microsoft DirectX的一部分。DirectX是一套由微软开发的多媒体编程接口&#xff0c;用于游戏和多媒体应用的开发。d3dcompiler_47.dll文件主要用于对DirectX编译器的调用&#xff0c;它包含了Direct3D着色器编译…