[Java EE] 多线程(六):线程池与定时器

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (90平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(93平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

1. 线程池

1.1 什么是线程池

我们前面提到,线程的创建要比进程开销小,但是如果线程的创建/销毁比较频繁,开销也会比较大.所以我们便引入了线程池,线程池的作用就是提前把线程都创建好,放到用户态代码中写的数据结构中,后面就可以随用随取.
线程池最大的好处就是减少每次启动,销毁线程的开销.线程的创建和销毁,需要通过用户态+内核态来配合完成,但是线程池只需要通过用户态即可完成,不需要内核态的配合.
在这里插入图片描述
在这里插入图片描述

举例说明:渣女小故事
有一位小姐姐是个渣女,创建和销毁线程就像一位小姐姐和他的男朋友分手了,再和下一位男朋友一点一点培养感情,而线程池就相当于,小姐姐有一个备胎池,他同时和好几个小哥哥培养感情,如果和其中一个分手了,就可以从备胎池中调用其他小哥哥,无缝衔接.
在这里插入图片描述

1.2 线程池的构造方法参数解释(面试常考)

线程池的类名是TreadPoolExecutor,其中他的构造方法中提供了丰富的参数类型,我们下面来一一解释一下这些参数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. corePoolSize:线程池中的核心线程数.
  2. maximumPoolSize:线程池中的最大线程数.等于核心线程数+非核心线程数,其中非核心线程数是根据当前任务的多少来动态管理的.(至于最大线程要设置为多少,这个没有一个固定的值,一般认为,和CPU的逻辑核心数有关,可能是n+1,2n等)
  3. keepAliveTime: 允许非核心线程数的最大空闲时间.超出最大空闲时间,就会被回收.
  4. unit:等待时间的时间单位,时,分,秒.
  5. workQueue:用于存放任务的阻塞队列.后续线程池内部的工作线程就会消费这个队列,从而完成任务的执行.
  6. threadFactory:线程创建工厂,参与具体的线程创建工作.通过不同的工厂创建出的不同线程相当于对一些属性进行了不同的初始化设置.
    针对线程工厂,我们又可以引出另一个话题:工厂设计模式.
    这种设计模式主要是针对解决构造方法的不足而创建出的一种设计模式.
    比如,我们给出了一个点(Point)类:
    我们知道,一个点想要表示出来,一种是通过笛卡尔坐标系表示,一种是通过极坐标的方式来表示,但是我们会发现,笛卡尔坐标系的参数和极坐标系的参数个数相同,而且它们的参数类型也相同,这就使得我们不可以用构造方法来构造这个点.
public class Point {
    public double x = 0;
    public double y = 0;
    public double r = 0;
    public double a = 0;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public Point(double r,double a){
        this.a = a;
        this.r = r;
    }
}

编译报错:
在这里插入图片描述
所以我们便引入了工厂设计模式来解决这个问题:
我们引入PointBuilder这个类,通过PointBuilder来构造这个点,并在Point这个点中设置set方法.

class Point {
    private double x = 0;
    private double y = 0;
    private double r = 0;
    private double a = 0;

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }

    public void setR(double r) {
        this.r = r;
    }

    public void setA(double a) {
        this.a = a;
    }
}
class PointBuilder{
    public static Point pointXY(double x,double y){
        Point p = new Point();
        p.setX(x);
        p.setY(y);
        return p;
    }
    public static Point pointRA(double r,double a){
        Point p = new Point();
        p.setA(a);
        p.setR(r);
        return p;
    }
}
  1. handler:任务超出线程池的承受范围的拒绝策略
    • AbortPolicy():超过负荷,直接抛出异常.
    • CallerRunsPolicy():调用者负责处理多出来的任务.
    • DiscardOldestPolicy():丢弃队列中最老的任务.
    • DiscardPolicy():丢弃新来的任务.

举例说明:年会不能停
有请老朋友:马杰克,潘妮,杰弗瑞,胡建林
corePoolSize:相当于公司中的正式员工,比如马杰克,杰弗瑞,胡建林.
maximumPoolSize:相当于公司中的所有员工,包括正式员工+外包.
keepAliveTime:允许外包员工的最大空闲时间,比如允许潘妮的最大空闲时间,一旦超过最大空闲时间,杰弗瑞就会把潘妮开除掉.
unit:允许潘妮的空闲时间单位,时,分,秒.
workQueue:每次员工们领取工作任务的账号.
threadFactory:公司不同的HR,通过HR招人(创建线程)
handler:员工们任务太多时候的拒绝策略
比如有一天杰弗瑞需要和胡建林要去干一场直播,但是胡建林由于工作太多,它需要拒绝杰弗瑞,其中胡建林有多种拒绝他的策略.第一种方法就是:胡建林直接罢工,工作和直播都不干了,就是直接罢工了.(AbortPolicy()).第二种方法就是:胡建林让杰弗瑞自己去直播(CallerRunsPolicy()).第三种方法就是:胡建林可以先放下手头的工作,和杰弗瑞去直播(DiscardOldestPolicy()).第四种方法就是:胡建林可以先完成自己的工作,之后在去和杰弗瑞直播(DiscardPolicy())
在这里插入图片描述

1.3 标准库中的线程池

Java源码的编写者也知道,上面这个方法用起来特别难,因为有许多参数.所以Java的标准库中提供了创建线程的一个工厂类Executors(本质上是Executor的封装),其中有一些创建线程的方法.这些方法的返回值可以直接当做线程池来使用,返回值的类型为ExecutorService.

  • 线程池中的核心方法为submit,它是为线程池的阻塞队列中添加Runnable对象.
  • 创建线程的工厂类Executor中右许多创建线程的方法,不同方法返回的线程属性各不相同.
    • newFixedTreadPool(int nTread):创建固定线程数的线程池
    • newCachedTreadPool():创建线程数目动态变化的线程池.(随任务数量变化)
    • newSingleTreadExectuor():创建单线程的线程池.
    • newScheduleTreadPool():设定延迟时间后执行指令,或者定期执行指令,是进阶版的Timer.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo24 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        //固定创建10个线程,注意返回类型
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("tread"+ finalI + Thread.currentThread().getName());
                }//只有固定的10个线程在执行这100个任务
            });
        }
        ExecutorService service1 = Executors.newCachedThreadPool();
        //随着任务的增多,线程创建增多
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            service1.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("tread"+ finalI + Thread.currentThread().getName());
                }
            });
        }
    }
}

创建固定线程线程池的运行结果:
在这里插入图片描述
我们看到,submit方法提供了100个任务,但是执行这100个任务的只有10个线程在执行.

下面是创建一个现场数量动态变化的线程池:
在这里插入图片描述
我们看到,线程池中被创建出了很多线程,100个任务由很多线程来执行.

1.4 实现线程池

  • 首先我们要实现核心方法submit,将任务添加到线程池的队列中.
  • 使用Worker类描述一个工作线程.使用Runnable描述一个任务.
  • 使用BlockingQueue组织所有的任务.
  • 每个Worker线程需要做的事情就是不断从BlockingQueue中获取任务并执行.
  • 指定线程池的最大线程数.
  • submit方法时生产者,不断往队列中添加Runnable元素,而构造方法是消费者,不停地从队列中获取Runnable元素并执行.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 创建固定线程的线程池
 */
public class MyTreadPool {
    public BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    //通过阻塞队列来保存可运行对象
    //向阻塞队列提交任务,生产者
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
    //构造方法创建线程来执行队列中的任务.消费者
    public MyTreadPool(int nTread) {
        for (int i = 0; i < nTread; i++) {
            Thread thread = new Thread(()->{
                while (true){//每个线程不停地从队列中取出元素
                    try {
                        queue.take().run();//获取任务清单
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            thread.start();//创建完线程之后立即启动
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTreadPool myTreadPool = new MyTreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            myTreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Tread"+ finalI + Thread.currentThread().getName());
                }
            });
        }

    }
}

运行结果:
在这里插入图片描述

2. 定时器

2.1 概念

指定一个任务(Runnable),并且指定一个时间,此时这个任务不会立即执行,而是在时间到达之后再执行.
在这里插入图片描述
定时器在我们日常的开发中也非常常见.比如在双11,0点开始定时抢购,再比如如果网络超过5000ms无响应的时候,就会尝试重新连接等.

2.2 标准库中的定时器

  • 标准库中提供一个Timer类.Timer的核心方法为schedule,为Timer的队列中添加元素.
  • schedule包含两个参数,第一个参数指定将要执行的任务代码,第二个参数指定多长时间执行.
import java.util.Timer;
import java.util.TimerTask;

public class Demo25 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },3000);//延迟3s后再执行
    }
}

2.3 定时器的模拟实现

  • 队列中使用优先级队列实现,把时间作为优先级队列的比较规则,delay时间短的放在堆顶.(注意不要使用PriorityBlockingQueue,容易死锁)
  • 队列中的每一个元素是一个Task对象.
  • Task中带有一个时间属性.
  • 同时有一个Worker线程一直扫描队首元素,通过与当前系统的时间戳去比较,看队首元素是否到达执行时间.
  • 其次,由于我们使用的是优先级队列,其中一定存在线程安全问题,我们需要使用synchronized对其进行加锁.
import java.util.PriorityQueue;

/**
 * 描述一个任务类
 */
class TimerTask implements Comparable<TimerTask>{
    public Runnable runnable;
    public long time;

    public TimerTask(Runnable runnable, int delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(TimerTask o) {
        return (int)(this.time-o.time);//这里谁减谁不要记,试一试就知道了
    }
}
/**
 * 定时器
 */
public class MyTimer {
    public PriorityQueue<TimerTask> priorityQueue = new PriorityQueue<>();

    /**
     * 该方法为优先级队列提供任务,生产者
     * @param runnable 可运行对象
     * @param delay 延时时间
     */
    public void schedule(Runnable runnable,int delay){
        synchronized (this){//添加元素时加锁
            priorityQueue.offer(new TimerTask(runnable,delay));
            this.notify();//唤醒构造方法的wait
        }
    }

    /**
     * 创建线程执行队列中的任务,消费者
     */
    public MyTimer() {
        Thread thread = new Thread(()->{
            while (true){//循环扫描堆顶元素,判断是否要执行
                synchronized (this){//由于优先级队列的不安全性,所以加锁
                    if (priorityQueue.isEmpty()){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    long cur = System.currentTimeMillis();//注意保存当前时间,不可以写在判断时间中
                    //否则每次都在改变
                    TimerTask task = priorityQueue.peek();
                    if (cur >= task.time){
                        priorityQueue.poll().runnable.run();
                    }else {
                        try {
                            this.wait(task.time-cur);//注意等待时间是任务时间减去当前是时间
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        thread.start();//注意启动线程
    }
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },2000);
    }
}

注意事项:

  • 不可以把wait()那一行使用continue代替,否者就会出现"忙等"的状态.
  • wait()处不可以用sleep()代替,否者就把锁抱死了,应该在休眠的时候让其他线程继续调度系统资源.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

语义分割——铁路轨道数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 重…

NASA数据集——NOAA 气溶胶和海洋科学考察数据(AEROSE)

Saharan Dust AERosols and Ocean Science Expeditions 简介 NOAA 气溶胶和海洋科学考察&#xff08;AEROSE&#xff09;是一种基于测量的综合方法&#xff0c;用于了解热带海洋上空气溶胶长程飘移的影响&#xff08;Morris 等人&#xff0c;2006 年&#xff1b;Nalli 等人&a…

直流屏整流模块HG07A220R电源模块HG10A220R

直流屏整流模块HG07A220R电源模块HG10A220R 其他同类型监控模块PM09T电源模块HG22005/S&#xff0c;HG22010/S&#xff0c;HG11010/S&#xff0c;HG11020/S&#xff0c;HG10A220Z&#xff0c;HG10A220F&#xff0c;HG05A220Z&#xff0c;HG07A220Z&#xff0c;HG10A110Z&#x…

Electron 对 SQLite 进行加密

上一篇讲了如何在 Electron使用 SQLite&#xff0c;如果 SQLite 中存有敏感数据&#xff0c;客户端采用明文存储风险很高&#xff0c;为了保护客户数据&#xff0c;就需要对数据进行加密&#xff0c;由于 electron 对代码并不加密&#xff0c;所以这里排除通过逆向工程进行数据…

从论文中看AI绘画

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 主要看是看Diffusion Models,CLIP,ControlNet,IP-Adapter这种经典论文,尝试总结论文写作的一些方式以及图像生成模型的一些内在思想. 对于其中的数学原理和代码不过深究. DDPM 使用扩散模型得到高质量图像,证明了这…

三、Linux基础命令

章节目标 了解Linux系统注意事项掌握Linux基础命令知道vmware tools的作用 一、Linux系统使用注意 1. Linux严格区分大小写 Linux 和Windows不同&#xff0c;Linux严格区分大小写的&#xff0c;包括文件名和目录名、命令、命令选项、配置文件设置选项等。例如&#xff0c;在…

5.3 调制与解调

信号的调制与解调是通信系统中一对基本的概念&#xff0c;涉及将信息&#xff08;语音、视频、数据等&#xff09;在发送之前进行处理以便在传输介质&#xff08;如无线电波、电话线等&#xff09;上有效传输&#xff0c;以及在接收端恢复这些信息的过程。 一、调制&#xff0…

Leetcode—289. 生命游戏【中等】

2024每日刷题&#xff08;126&#xff09; Leetcode—289. 生命游戏 算法思想 实现代码 class Solution { public:void gameOfLife(vector<vector<int>>& board) {int rows board.size();int cols board[0].size();int neighbors[3] {0, 1, -1};vector<…

spring框架学习记录(2)

文章目录 注解开发bean相关注解开发定义bean纯注解开发纯注解开发中bean的管理 依赖注入相关依赖注入第三方bean管理第三方bean依赖注入 AOP(Aspect Oriented Programming)面向切面编程AOP简介AOP核心概念AOP工作流程AOP切入点表达式通知类型AOP通知获取数据 注解开发 bean相关…

Day19 代码随想录打卡|字符串篇---反转字符串II

题目&#xff08;leecode T541&#xff09;&#xff1a; 给定一个字符串 s 和一个整数 k&#xff0c;从字符串开头算起&#xff0c;每计数至 2k 个字符&#xff0c;就反转这 2k 字符中的前 k 个字符。 如果剩余字符少于 k 个&#xff0c;则将剩余字符全部反转。如果剩余字符小…

OceanBase 轻量级数仓关键技术解读

码到三十五 &#xff1a; 个人主页 为了更好地聚合和治理跨域数据&#xff0c;帮助企业用较低的成本快速聚合分析&#xff0c;快速决策&#xff0c;不断的让企业积累的数据产生价值&#xff0c;从全域海量数据抓取&#xff0c;高性能流批处理&#xff0c;元数据血缘治理等等方面…

数据分析从入门到精通 1.numpy 剑客修炼

会在某一瞬间突然明白&#xff0c;有些牢笼是自己给自己的 —— 24.5.5 一、数据分析秘笈介绍 1.什么是数据分析 是把隐藏在一些看似杂乱无章的数据背后的信息提炼出来&#xff0c;总结出所研究对象的内在规律。使得数据的价值最大化 案例&#xff1a; 分析用户的消…

Kotlin: Expecting a ‘>‘

数组值为任意类型&#xff0c;声明报错: Kotlin: Expecting a > var anyArr1: Array<Any?> arrayOf("a", "b", "c", true, 34)原因是&#xff1a; // var anyArr1: Array<Any?> arrayOf("a", "b", "c…

概念解析 | 威胁建模与DREAD评估:构建安全的系统防线

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:威胁建模和DREAD模型 概念解析 | 威胁建模与DREAD评估:构建安全的系统防线 What Is Threat Modeling? Definition, Process, Examples, and Best Practices - Spic…

蓝桥杯-路径之谜

题目描述 小明冒充X星球的骑士&#xff0c;进入了一个奇怪的城堡。城堡里面什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡的地面时n*n个方格。如下图所示。 按习俗&#xff0c;骑士要从西北角走到东南角。可以横向或者纵向移动&#xff0c;但是不能斜着走&#x…

SpringBoot自定义定时任务

通常&#xff0c;在我们的项目中需要定时给前台发送一些提示性消息或者我们想要的定时信息&#xff0c;这个时候就需要使用定时任务来实现这一功能&#xff0c;实现也很简单&#xff0c;接下来具体来看看吧~ 简单定时任务 首先&#xff0c;你需要在你的启动类上加上开启定时任…

贪吃蛇(下)游戏的实现

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 文章目录 前言一.蛇和食物的打印二.游戏的运行逻辑三.结束游戏 &#xff08;善后工作&#xff09;四.游戏的测…

K8S-Dashboard安装并创建普通用户

参考&#xff1a;在centos stream 9上搭建k8s最新版本&#xff08;当前&#xff1a;v1.26.1&#xff09;集群环境 查找dashboard 对应的版本 https://github.com/kubernetes/dashboard/releases 下载 kubernetes-dashboard.yaml 使用的2.7.0 wget https://raw.githubuserconte…

mac安装虚拟机linux系统

需要下载的有&#xff1a;centos8镜像 , 虚拟器 VMware 软件包 , Termius 或者xshell 1. CentOS系统下载 linux系统一般有&#xff1a; CentOS、ubuntu、redhat&#xff0c;选择一种进行安装就可以 CentOS 2024 年开始停止维护和发布 CentOS8的下载与安装(windows下安装) 镜…

【网络安全产品】---应用防火墙(WAF)

what Web应用防火墙&#xff08;Web Application Firewall) WAF可对网站或者App的业务流量进行恶意特征识别及防护&#xff0c;在对流量清洗和过滤后&#xff0c;将正常、安全的流量返回给服务器&#xff0c;避免网站服务器被恶意入侵导致性能异常等问题&#xff0c;从而保障…
最新文章