JavaEE初阶-多线程5

文章目录

  • 一、线程池
    • 1.1 线程池相关概念
    • 1.2 线程池标准类
    • 1.3 线程池工厂类
    • 1.4 实现自己的线程池
  • 二、定时器
    • 2.1 java标准库中的定时器使用
    • 2.2 实现一个自己的定时器
      • 2.2.1 定义任务类
      • 2.2.2 定义定时器


一、线程池

1.1 线程池相关概念

池这个概念在计算机中比较常见,常量池、数据库连接池、线程池、进程池、内存池…思想都是类似的。为什么我们要在多线程这里引入线程池,且听我娓娓道来。
并发编程中我们嫌弃进程的创建销毁开销大,于是我们使用线程。但是随着时代的发展,频繁创建销毁线程的开销也越来越明显了,对于此种情况优化的方式之一就是线程池。为什么使用线程池之后能够提升效率,其中的关键点就在于直接创建/销毁线程的操作是内核态配合用户态的工作,而线程池中的线程的创建/销毁只涉及到用户态不需要内核态的配合,因此能够提升效率。这里需要明白一点就是系统内核是不可控的,他要完成的工作很多,所以在用户态和其配合的过程中很可能它去先忙别的工作,从而拖慢逻辑执行的时间,因此单独涉及用户态的操作效率要比用户态和内核态配合的工作效率高。
如果使用线程池,会提前把线程建好,然后将线程保存到用用户态代码编写的数据结构中,后面需要用到线程的时候直接从池子中取,不用就放回去,这个过程完全是用户态,不需要和内核进行交互。

1.2 线程池标准类

java中线程池标准类为ThreadPoolExecutor,参数比较复杂,相对的对线程池的创建的控制就比较精细。参数如下图:
在这里插入图片描述
在这里插入图片描述
首先看前两个参数,标准库的线程池是这样设定的,把线程分为两类分别为核心线程以及非核心线程。举个例子来说核心线程相当于公司里的老员工,非核心相当于公司里的实习生,平时负责业务的都是老员工,但是当人手不够的时候会让实习生帮忙。标准库的线程池中的corePoolSize以及maxinumPoolSize也是这样类似的思想。corePoolSize表示核心线程,线程池最开始创建时就带有这么多线程,maxinumPoolSize代表最大线程数,线程池有一个方法submit,通过这个方法可以提交任务到线程池让线程池处理,当线程池的任务多到它自己忙不过来时就会创建新的线程来帮助处理任务,新线程和核心线程加起来的数目不能超过这里指定的最大线程数。当任务没那么多的时候线程池就会释放这些建立的新线程,回收只会回收新建立的线程不会回收核心线程,最终的线程数量肯定是大于等于核心线程数量的。
在实际开发中需要设置多少的线程数不仅和你的配置有关,还跟你的程序特点有关。
程序一般分为两种,第一种就是cpu密集型,如下图你代码的逻辑都需要cpu来完成,一旦程序跑起来一下就能占满一个cpu核心,因此在这种情况下你线程数不能超过cpu的逻辑核心的数目。
在这里插入图片描述
第二种程序是IO密集型程序,你的代码大部分都是在等待IO。(等待IO时不占用cpu,不被调度)你的代码此时应该考虑的不是cpu而是其它的事情,例如如果你的代码是一个网络程序,那么就需要考虑网卡的带宽。硬盘IO也是类似。
在这里插入图片描述
上述两个模型都太理想了,真正开发时一般程序会在IO密集型和cpu密集型之间,此时就需要去写代码实验,从而确定多少的核心线程数的效果最好。
在这里插入图片描述
这里的两个参数是配合起来指定非核心线程在空闲时可以存在的时间,非核心线程空闲时不是立即回收,而是等线程池不忙的时候回收。unit是枚举类型指定时间的基本单位,keepAliveTime指定单位时间的数目,总时间就是非核心线程可以存在的时间。unit可以提供的时间单位如下图:
在这里插入图片描述
在这里插入图片描述
这里的参数指的就是线程池中的任务队列,线程池会提供submit方法让其它线程将任务提交给线程池。线程池中需要队列这样的数据结构,来将任务保留起来,后面线程池中的工作线程就会消费队列中的任务并且执行任务的具体内容。
在这里插入图片描述
上图中的参数看名字就知道是一个和工厂模式相关的参数,实际上这里就是标准库提供的用来设置线程池创建的线程的一些属性的工厂类,一般就是配合线程池使用,一般使用线程池标准类时这里可以使用默认的参数。然后工厂模式就是一种设计模式,工厂方法就是将构造函数进行一层包装并返回对象,专门用来包装构造方法的类就是工厂类。
最后一个参数最重要的参数,如下图。这个参数也是一个枚举类型,就是用来表面在向线程池提交任务时,线程池采用的是哪种拒绝策略。
在这里插入图片描述
每种拒绝策略对应的参数如下图:
在这里插入图片描述
第一个就是当提交任务被线程池拒绝时,要直接抛出异常“罢工”。第二个参数是当线程池拒绝时,将任务交给提交任务的线程去执行。第三个参数是当线程池拒绝提交的任务时,就将任务队列中的最老的任务丢弃,将这个任务加入线程池中的任务队列。第四个参数则是将任务队列中最新的任务给丢弃,将这个任务加入到线程池中的任务队列。

1.3 线程池工厂类

因为java标准库自己也知道ThreadPoolExecutor使用起来比较费劲,所以提供了创建线程池的工厂类Executors,这样使用起来简单很多,但是操作的精细度就不如标准的线程池的类。
在这里插入图片描述
如上图都是建立线程池的工厂方法,Executors.newCachedThreadPool()就是创建普通的线程池,根据任务的数目来对线程进行扩容。Executors.newFixedThreadPool(10)创建固定线程数的的线程池,线程数不能增加也不能减少。Executors.newScheduledThreadPool()这个也是创建固定线程数的线程池,但是其中的任务延时执行。Executors.newSingleThreadExecutor()是创建单个线程的线程池。注意这里设置的线程数都是最大线程数而并非是核心线程数。
下图给出工厂类建立线程池并使用的过程:
在这里插入图片描述
下图给出使用标准线程池类的过程:
在这里插入图片描述
需要注意的一点是线程池中的线程都是前台线程,当main线程执行结束时进程不会结束会等线程池中的线程全都执行完毕。

1.4 实现自己的线程池

我们要实现自己的线程池要考虑哪些东西?
(1)线程池中需要若干个线程。
(2)要有存放任务的队列。
(3)要提供提交任务的submit方法。
因为这里只是实现以下线程池的简单代码帮助理解线程池,所以代码中并未实现拒绝策略以及回收线程等操作,代码如下:

package Thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

class MyThreadPool {
    //注意细节 LinkedBlockingQueue时添加元素会自动扩容导致添加时不会堵塞 只有移除元素时可能会堵塞
    //ArrayBlocking不会自动扩容 添加和删除元素都有可能堵塞
    private ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    private int threadMaxSize = 0;
    private List<Thread> list = new ArrayList<>();

    // 初始化线程池
    public MyThreadPool(int coreSize, int threadMaxSize) {

        this.threadMaxSize = threadMaxSize;
        for (int i = 0; i < coreSize; i++) {
            // 创建若干个线程
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            Runnable runnable = queue.take();
                            runnable.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
            });

            t.start();
            list.add(t);
        }

    }

    // 把任务添加到线程池中
    public void submit(Runnable runnable) throws InterruptedException {
        // 此处进行判定, 判定说当前任务队列的元素个数, 是否比较长.
        // 如果队列元素比较长, 说明已有的线程, 不太能处理过来了. 创建新的线程即可.
        // 如果队列不是很长, 没必要创建新的线程.
        queue.put(runnable);
        if (list.size() < threadMaxSize && queue.size() >= 500) {
            //创建新线程
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            Runnable task = queue.take();
                            task.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });

            t.start();
            list.add(t);
        }
    }
}

public class Demo38 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(10, 20);

        for (int i = 0; i < 10000; i++) {
            int temp = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello" + temp + "," + Thread.currentThread().getName());
                }
            });
        }

    }

}

二、定时器

定时器就是“闹钟”的效果,指定一个时间,再指定一个任务,此时这个任务不会立即执行,而是等时间到达之后再去执行。定时器是日常开发中非常重要的组件。举个例子,短信验证码,验证码只在五分钟内是有效的,发送验证码时会将验证码保存起来。设定定时器,会在五分钟延时之后执行删除验证码的逻辑。定时器在未来开发中非常重要,被封装成服务器供整个分布式系统使用。

2.1 java标准库中的定时器使用

在这里插入图片描述
通过timer对象的schedule方法指定要执行的任务以及要延时的时间,其中的TimerTask就是类似于Runnable的抽象类,执行效果如下:
在这里插入图片描述
以上结果符合延时的效果,但是执行完进程并未结束,应该是因为定时器中的线程是前台线程。

2.2 实现一个自己的定时器

编写代码首先要确定两个需求,写一个定时器:
(1)能够延时执行任务/指定时间执行任务。
(2)能够管理多个任务。

2.2.1 定义任务类

模仿标准库定时器的模式,我们也定义一个任务类来表示要执行的任务。在定时器的实现中我们在定义的任务中设定任务需要执行的绝对时间,为了后续代码执行时可以方便的判断,该任务是否应该执行。如果保存相对时间的话就比较麻烦。这里可以举一个例子。就比如说领导叫你去汇报工作,第一种说法是叫你三十分钟后去,这种情况你还要根据当前的时间进行换算。第二种说法是直接叫你五点半去,就不需要换算了。
在这里插入图片描述
任务类代码编写如上图,实际上就是包装了Runnable接口,然后在类里面通过设定的参数确定了任务要执行时的时间,然后还重写了一个为了方便给后续优先级任务队列按时间从小到大排序的Comparable接口的compareTo方法。

2.2.2 定义定时器

定时器编写首先要关注的就是保存任务的数据结构,我们使用按时间排序的优先级队列,这样出队的队列就是最早的任务,其它任务都未到时间。如果不这样写数据结构的话,那么后续执行任务的线程就要通过循环来不断遍历这里的数据结构来找到满足执行时间的任务。
在这里插入图片描述

定时器中还需要线程去执行任务,在类的构造方法中来创建新的线程去执行队列中的任务,线程会先拿出队首任务,判断是否满足时间,如果满足则执行不满足则等待相差的时间之后再执行,这个过程是通过while循环实现的,另外当队列为空时线程也要等待,因为此时没有任务需要执行。
在这里插入图片描述
然后是schedule方法的编写,传入任务以及相对时间后建立任务对象然后将任务对象添加到队列中,还要对进入WAITING状态的线程进行一对一唤醒。对于陷入WAITING状态的线程有两种可能,第一种就是队列为空,此时添加了新任务到队列中当然应该唤醒。第二种就是新加入了任务,要唤醒线程进行一次判断有没有符合执行时间的任务,因为新添加的那个任务可能就是符合执行时间的任务。
在这里插入图片描述
注意:因为这里使用的队列不是线程安全的,并且有两个线程在修改队列,一个是使用schedule方法的线程,另一个是定时器构造方法中的线程,所以就像图中代码写的要给定时器构造方法以及schedule方法加锁。为什么不直接用PriorityBlockingQueue这样的队列?因为使用它只能处理队列为空的阻塞,对于一些执行时间相关的阻塞也无能为力,你要使用wait还要在外面写个锁,阻塞队列内部还有锁,两个锁很容易发生死锁,为了便于理解及编写就直接使用了普通的优先级队列。
在这里还是补一下定时器的代码:

package Thread;

import java.util.PriorityQueue;


class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable;
    //这里的time是绝对时间
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //绝对时间,当前时间加上需要延迟的时间长度
        this.time = System.currentTimeMillis() + delay;

    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    //优先级队列存储任务可以根据时间的前后来输出从而避免让线程去遍历
    PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    //因为这里的线程修改任务队列以及schedule方法的线程也会修改队列就会出现线程安全问题,故而加锁
                    synchronized (this) {
                        //当任务队列为空的时候,线程先等着任务加入再执行
                        if (queue.size() == 0) {
                            this.wait();
                        }
                        MyTimerTask task = queue.peek();
                        long curTime = System.currentTimeMillis();
                        //达到任务开始的时间直接执行并在队列中删除任务
                        if (curTime >= task.getTime()) {
                            task.run();
                            queue.poll();
                        } else {
                            //未达到任务开始的时间线程等待从而避免执行多次循环占满cpu
                            this.wait(task.getTime()-curTime);
                        }

                    }

                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, Long delay) {
        //两个线程修改队列故而加锁
        synchronized (this) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            //加入新任务后要唤醒线程
            this.notify();
        }

    }

}

public class Demo40 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(3000);
            }
        }, 3000L);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(2000);
            }
        }, 2000L);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(1000);
            }
        }, 1000L);


    }

}

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

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

相关文章

PXE+Kickstart无人值守安装安装Centos7.9

文章目录 一、什么是PXE1、简介2、工作模式3、工作流程 二、什么是Kickstart1、简介2、触发方式 三、无人值守安装系统工作流程四、实验部署1、环境准备2、服务端&#xff1a;关闭防火墙和selinux3、添加一张仅主机的网卡4、配置仅主机的网卡4.1、修改网络连接名4.2、配IP地址4…

​​​【收录 Hello 算法】5.3 双向队列

目录 5.3 双向队列 5.3.1 双向队列常用操作 5.3.2 双向队列实现 1. 基于双向链表的实现 2. 基于数组的实现 5.3.3 双向队列应用 5.3 双向队列 在队列中&#xff0c;我们仅能删除头部元素或在尾部添加元素。如图 5-7 所示&#xff0c;双向队列&#xff08…

校园志愿者管理系统带万字文档

文章目录 校园志愿者管理系统一、项目演示二、项目介绍三、10000字论文参考四、部分功能页面五、部分代码展示六、底部获取项目源码和万字论文参考&#xff08;9.9&#xffe5;带走&#xff09; 校园志愿者管理系统 一、项目演示 校园志愿者管理系统 二、项目介绍 基于Spring…

【数据结构】-- 相交链表-环形链表

交叉链表 . - 力扣&#xff08;LeetCode&#xff09; 如果链表的两条链的长度一样&#xff0c;链表两端对齐&#xff0c;解决这个问题将会变得非常简单&#xff0c;直接分别遍历两个链表&#xff0c;想等时的节点即为所求。我们想办法让链表对齐--分别从a和b遍历链表&#xff…

centos7中如何全局搜索一下nginx的配置文件?

在CentOS 7中搜索Nginx的配置文件&#xff0c;你可以使用一些常用的命令行工具&#xff0c;比如find、grep等。这些工具可以帮助你在文件系统中查找文件&#xff0c;也可以用来查找Docker容器内部的文件&#xff0c;只要你知道如何访问容器的文件系统。 1. 搜索系统中的Nginx配…

nowcoder——回文结构

链表的回文结构_牛客题霸_牛客网 (nowcoder.com) 我们来分析该题&#xff1a;我们首先要清楚什么是回文结构&#xff1f;其实就是对称结构。如果一个链表呈对称结构就说明该链表具有回文结构。 下面给上一些例子&#xff1a; 那我们怎么判断该链表是否属于回文结构呢&#xf…

Web3 Tools - Base58

Base58编码 Base58编码是一种用于表示数字的非常见的编码方法。它通常用于加密货币领域&#xff0c;例如比特币和其他加密货币的地址表示。 什么是Base58编码&#xff1f; Base58编码是一种将数字转换为人类可读形式的编码方法。与常见的Base64编码不同&#xff0c;Base58编码…

AI智能体|我把Kimi接入了个人微信

大家好&#xff0c;我是无界生长。 最近加入AI学习交流群的小伙伴越来越多&#xff0c;我打算在微信群接入一个聊天机器人&#xff0c;让它协助管理微信群&#xff0c;同时也帮忙给群友解答一些问题。普通的群聊机器人肯定是不能满足需求的&#xff0c;得上AI大模型&#xff0c…

使用 Python 中的 TensorFlow 检测垃圾短信

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

绘唐3启动器怎么启动一键追爆款3正式版

绘唐3启动器怎么启动一键追爆款3正式版 工具入口 一.文案助手&#xff1a; 【注意&#xff01;&#xff01;】如果图片无显示&#xff0c;一般情况下被杀毒拦截&#xff0c;需关闭杀毒软件或者信任文件路径。 win10设置排除文件&#xff1a; 1.【新建工程】使用前先新建工程…

【Flutter】极光推送配置流程(VIVO/OPPO/荣耀厂商通道) 章三

前言 很高兴大家来看小编写的文章&#xff5e;&#xff5e; 继【Flutter】极光推送配置流程(极光通道/华为厂商/IOS) 章一 继【Flutter】极光推送配置流程(小米厂商通道) 章二 接下配置VIVO/OPPO/华为荣耀的厂商通道 所有截图来源于公司项目&#xff0c;所以会有大量马赛克&am…

5.13作业

使用消息队列实现的2个终端之间的互相聊天 并使用信号控制消息队列的读取方式&#xff1a; 当键盘按ctrlc的时候&#xff0c;切换消息读取方式&#xff0c;一般情况为读取指定编号的消息&#xff0c; 按ctrlc之后&#xff0c;指定的编号不读取&#xff0c;读取其他所有编号的…

Pikachu 靶场 URL 重定向通关解析

前言 Pikachu靶场是一种常见的网络安全训练平台&#xff0c;用于模拟真实世界中的网络攻击和防御场景。它提供了一系列的实验室环境&#xff0c;供安全专业人士、学生和爱好者练习和测试他们的技能。 Pikachu靶场的目的是帮助用户了解和掌握网络攻击的原理和技术&#xff0c;…

Qt与QWebEngineView 交互-调试窗口-JS拓扑图完整示例参考

1&#xff1a;介绍&#xff1a; Qt与QWebEngineView的交互 简介之前文章解释过&#xff0c;链接在下面 传送门&#xff1a;Qt与QWebEngineView 交互完整示例参考_qt qwebview-CSDN博客 一般在使用这种方式时&#xff0c;可能会出现各种问题而不好调试&#xff0c;如果能够像…

【408精华知识】提高外部排序速度的三种方式

文章目录 一、败者树二、置换-选择排序三、最佳归并树 一、败者树 还没写完… 二、置换-选择排序 三、最佳归并树 写在后面 这个专栏主要是我在学习408真题的过程中总结的一些笔记&#xff0c;因为我学的也很一般&#xff0c;如果有错误和不足之处&#xff0c;还望大家在评…

基于Echarts的大数据可视化模板:服务器运营监控

目录 引言背景介绍研究现状与相关工作服务器运营监控技术综述服务器运营监控概述监控指标与数据采集可视化界面设计与实现数据存储与查询优化Echarts与大数据可视化Echarts库以及其在大数据可视化领域的应用优势开发过程和所选设计方案模板如何满足管理的特定需求模板功能与特性…

I. Integer Reaction

Problem - I - Codeforces 看到最小值最大值&#xff0c;二分答案。 思路&#xff1a;每次二分时开两个集合&#xff0c;分别表示 0 0 0颜色和 1 1 1颜色。如果是 c c c颜色&#xff0c;先将值存入 c c c颜色&#xff0c;之后在 ! c !c !c颜色中找大于等于 m i d − a mid - a…

.NET开源、功能强大、跨平台的图表库LiveChart2

LiveCharts2 是 从LiveCharts演变而来,它修复了其前身的主要设计问题,它专注于在任何地方运行,提高了灵活性,并继承LiveCharts原有功能。 极其灵活的数据展示图库 (效果图) 开始使用 Live charts 是 .Net 的跨平台图表库,请访问 https://livecharts.dev 并查看目标平…

括号匹配(栈)

20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; c有栈 但是C语言没有 到那时我们可以自己造 这里的代码是直接调用栈&#xff0c;然后调用 等于三个左括号的任意一个 我们就入栈 左括号&#xff08;入栈&#xff09; 右括号 取出栈顶数据&#xff0c;出栈并且进行匹配…

用Transformers实现简单的大模型文本生成

根据输入的prompt&#xff0c;生成一段指定长度的文字。Llama跑起来太慢了&#xff0c;这里用GPT-2作为列子。 from transformers import GPT2LMHeadModel, GPT2Tokenizer import torchtokenizer GPT2Tokenizer.from_pretrained("gpt2") model GPT2LMHeadModel.fr…