Java并发编程—线程池

news2024/10/5 15:32:41

文章目录

  • 线程池
    • 什么是线程池
      • 线程池优点:
      • 线程复用技术
    • 线程池的实现原理是什么
    • 线程池执行任务的流程?
      • 线程池如何知道一个线程的任务已经执行完成
    • 线程池的核心参数
      • 拒绝策略
    • 线程池类型(常用线程池)
      • 阻塞队列
        • 执行execute()方法和submit()方法的区别
    • 代码
      • 进行优化
      • 线程池代码描述

——————————————————————————————————

线程池

什么是线程池

  • 首先,线程池本质上是一种池化技术,而池化技术是一种资源复用的思想,比较常见的有连接池、内存池、对象池。
  • 而线程池里面复用的是线程资源。
  • 线程池就是创建若干个可执行的线程放入一个池中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
  • 自己实现比较麻烦,所以有线程池帮助完成这些功能,让线程处于一直存活状态。
  • 有任务之后会交给线程池,线程池交给线程。

线程池优点:

①:减少线程的频繁创建和销毁带来的性能开销,因为线程创建会涉及到 CPU 上下文切换、内存分配等工作。
②:提升快速响应的能力,不需要创建线程,直接在线程池调用就行
③:线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用。
④:减少代码之间的耦合,能够实现异步操作(异步:直接发送下一个请求,不需要等待回复)

线程复用技术

线程复用技术,因为线程的生命周期时由任务运行的状态决定的,无法人为控制。所以为了实现线程的复用,线程池里面用到了阻塞队列,也就是说线程池里面的工作线程处于一直运行状态,它会从阻塞队列中去获取待执行的任务,一旦队列空了,那这个工作线程就会被阻塞,直到下次有新的任务进来。工作线程是根据任务的情况实现阻塞和唤醒,从而达到线程复用的目的。
最后,线程池里面的资源限制,是通过几个关键参数来控制的,分别是核心线程数、最大线程数。核心线程数表示默认长期存在的工作线程,而最大线程数是根据任务的情况动态创建的线程,主要是提高阻塞队列中任务的处理效率。

线程池的实现原理是什么

线程里面有个死循环
线程池里面存数据的集合是队列

线程池执行任务的流程?

  1. 线程池执行executelsubmit方法向线程池添加任务,当任务小于核心线程数corePoolSize,线程池中可以创建新的线程。
  2. 当任务大于核心线程数corePoolSize,就向阻塞队列添加任务。
  3. 如果阻塞队列已满,需要通过比较参数maximumPoolSize,在线程池创建新的线程,当线程数量大于maximumPoolSize,说明当前设置线程池中线程已经处理不了了,就会执行饱和策略。
    在这里插入图片描述

线程池如何知道一个线程的任务已经执行完成

(1)在线程池内部,当我们把一个任务丢给线程池去执行,线程池会调度工作线程来执行这个任务的 run 方法,run 方法正常结束,也就意味着任务完成了。
(线程池中的工作线程是通过同步调用任务的 run()方法并且等待 run 方法返回后,再去统计任务的完成数量。)
(2)如果想在线程池外部去获得线程池内部任务的执行状态,有几种方法可以实现。线程池提供了一个 isTerminated()方法,可以判断线程池的运行状态,我们可以循环判断 isTerminated()方法的返回结果来了解线程池的运行状态,一旦线程池的运行状态是 Terminated,意味着线程池中的所有任务都已经执行完了。想要通过这个方法获取状态的前提是,程序中主动调用了线程池的 shutdown()方法。在实际业务中,一般不会主动去关闭线程池,因此这个方法在实用性和灵活性方面都不是很好。
(3)在线程池中,有一个 submit()方法,它提供了一个 Future 的返回值,我们通过Future.get()方法来获得任务的执行结果,当线程池中的任务没执行完之前future.get()方法会一直阻塞,直到任务执行结束。因此,只要 future.get()方法正常返回,也就意味着传入到线程池中的任务已经执行完成了!

  • 也可以引入一个 CountDownLatch 计数器,它可以通过初始化指定一个计数器进行倒计时,其中有两个方法分别是 await()阻塞线程,以及 countDown()进行倒计时,一旦倒计时归零,所以被阻塞在 await()方法的线程都会被释放。基于这样的原理,我们可以定义一个 CountDownLatch 对象并且计数器为 1,接着在线程池代码块后面调用 await()方法阻塞主线程,然后,当传入到线程池中的任务执行完成后,调用countDown()方法表示任务执行结束。最后,计数器归零 0,唤醒阻塞在 await()方法的线程。
    (4)总结:不管是线程池内部还是外部,要想知道线程是否执行结束,我们必须要获取线程执行结束后的状态,而线程本身没有返回值,所以只能通过阻塞-唤醒的方式来实现,future.get 和 CountDownLatch 都是这样一个原理。

线程池的核心参数

线程池的真正实现类是 ThreadPoolExecutor,其构造方法需要如下参数:

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的 最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

拒绝策略

AbortPolicy(默认):丢弃任务,并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。

线程池类型(常用线程池)

  • 定长线程池(FixedThreadPool)
    • 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:控制线程最大并发数。
  • 定时线程池(ScheduledThreadPool )
    • 特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
    • 应用场景:执行定时或周期性的任务。
  • 可缓存线程池(CachedThreadPool)
    • 特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
    • 应用场景:执行大量、耗时少的任务。
  • 单线程化线程池(SingleThreadExecutor)
    • 特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
    • 应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

阻塞队列

在这里插入图片描述

执行execute()方法和submit()方法的区别

  • execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否:
  • submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get (long timeout,Timeunit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

代码

public class DequeThread<T extends Runnable> {
    //存储任务
    private Deque<Worker> threads=new LinkedList<>();
    private int defaultThreadCount=4;

    public DequeThread(){
        for (int i = 0; i < defaultThreadCount; i++) {
            Worker worker=new Worker();
            //当前类实例化的时候,不断在任务列表放任务
            threads.addLast(worker);
            //执行任务
            worker.start();
        }
    }
    private Deque<T> tasks=new LinkedList<>();

    public int  addTask(T task) {
        synchronized (tasks){//添加的时候也应该锁住
            tasks.addLast(task);//放任务
            tasks.notifyAll();
            //添加新任务时应该做一次唤醒,唤醒全部的来抢任务
        }
        return 0;
    }
    class Worker extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                T first=tasks.pollFirst();//取任务
                synchronized (tasks){//synchronized执行完之后,锁就释放出来了
                    //拿到锁就判断是否为空
                    while(first==null){//防止空指针
                        try {
                            tasks.wait();//为空的时候等待在task上
                            //.wait():让当前线程阻塞在对象上,并把锁释放掉
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //再判断是不是空,如果是空就进下一次循环
                        //应该是取出来再执行
                        first=tasks.pollFirst();
                    }
                }
                first.run();//执行任务
            }
        }
    }

    public static void main(String[] args) {
        DequeThread<Runnable> thread=new DequeThread<>();
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("1");
                    }
                }));
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("2");
                    }
                }));
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("3");
                    }
                }));
    }
}

进行优化

public class DequeThread<T extends Runnable> {
    //存储任务
    private Deque<Worker> threads=new LinkedList<>();
    private int defaultThreadCount=4;
    //限制个数,线程安全的计数
    private AtomicInteger taskCount = new AtomicInteger();
    private Object full = new Object();
    public DequeThread(){
        for (int i = 0; i < defaultThreadCount; i++) {
            Worker worker=new Worker();
            //当前类实例化的时候,不断在任务列表放任务
            threads.addLast(worker);
            //执行任务
            worker.start();
        }
    }
    private Deque<T> tasks=new LinkedList<>();

    public int  addTask(T task) throws InterruptedException {
        synchronized (tasks){//添加的时候也应该锁住
            synchronized (full){
                int i = taskCount.incrementAndGet();//自增
                //如果i加到10,就进行阻塞
                while (i > 10){
                    full.wait();
                }
            }
            tasks.addLast(task);//放任务
            //添加新任务时应该做一次唤醒,唤醒全部的来抢任务
            tasks.notifyAll();
        }
        return 0;
    }
    class Worker extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                synchronized (tasks){//synchronized执行完之后,锁就释放出来了
                    //优化——》tasks.pollFirst();放到锁里
                    T first=tasks.pollFirst();//取任务
                    //拿到锁就判断是否为空
                    while(first==null){//防止空指针
                        try {
                            tasks.wait();//为空的时候等待在task上
                            //.wait():让当前线程阻塞在对象上,并把锁释放掉
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //再判断是不是空,如果是空就进下一次循环
                        //应该是取出来再执行
                        first=tasks.pollFirst();
                    }
                    //优化——》first.run();放到锁里
                    first.run();//执行任务

                    //执行完成后,做减一的操作
                    taskCount.decrementAndGet();
                    //阻塞到full上的线程就能被唤醒
                    //如果空间满了,让调用的线程阻塞住
                    synchronized (full){
                        full.notifyAll();
                    }
                }
            }
        }
    }

    //main方法里面执行的代码结束了
    public static void main(String[] args) throws InterruptedException {
        DequeThread<Runnable> thread=new DequeThread<>();
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("1");
                    }
                }));
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("2");
                    }
                }));
        thread.addTask(new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("3");
                    }
                }));
    }
}

线程池代码描述

继承了Thread类,也可以实现Runnable接口,
如何让任务和线程关联起来?
选择了自己封装线程的run方法,让它和提交的任务关联起来;
又因为线程池的线程是优先于任务启动起来的,所以看不到任务,只能借助一个另外的集合private Deque tasks=new LinkedList<>(); tasks这个变量联系起来;
类似于生产者消费者的思想,没有任务的时候等着,有任务的时候执行,进而完成线程池的处理
而且线程池的初始化是在程序运行起来的时候就进行了
所以是线程池运行起来之后,才等着任务进来

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

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

相关文章

[附源码]计算机毕业设计springboot医疗纠纷处理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MySQL统计函数count详解

MySQL统计函数count详解1. count()概述2. count(1)和count(*)和count(列名)的区别3. count(*)的实现方式1. count()概述 count() 是一个聚合函数&#xff0c;返回指定匹配条件的行数。开发中常用来统计表中数据&#xff0c;全部数据&#xff0c;不为null数据&#xff0c;或者去…

yocto machine class解析之flashlayout-stm32mp

yocto machine class解析之flashlayout-stm32mp 上一篇文章中我们详细介绍了st-partitions-image class。里面根据配置生成了许多的分区镜像以及分区镜像的一些参数设置。本章节介绍的flashlayout class就会根据上面生成的这些参数来生成特定的.tsv刷机文件供ST的刷机工具使用…

Bootstrap5 容器

我们可以使用以下两个容器类&#xff1a; .container 类用于固定宽度并支持响应式布局的容器。.container-fluid 类用于 100% 宽度&#xff0c;占据全部视口&#xff08;viewport&#xff09;的容器。固定宽度 .container 类用于创建固定宽度的响应式页面。 注意&#xff1a…

[node文件的上传和下载]一.node实现文件上传;二、Express实现文件下载;三、遍历下载文件夹下的文件,拼接成一个下载的url,传递到前端

目录 一.node实现文件上传 1.FormData对象&#xff1a;以对象的方式来表示页面中的表单&#xff0c;又称为表单对象。以key:value的方式来保存数据&#xff0c;XMLHttpRequest对象可以轻松的将表单对象发送到服务器端 &#xff08;1&#xff09;是一个构造函数&#xff1a;ne…

LabVIEW在应用程序和接口中使用LabVIEW类和接口

LabVIEW在应用程序和接口中使用LabVIEW类和接口 LabVIEW类和接口是用户定义的数据类型。LabVIEW类和接口开发人员创建并发布这些数据类型。LabVIEW类或接口用户无需了解如何创建LabVIEW类或接口&#xff0c;但必须了解应用程序中通过类或接口定义的数据类型应当如何使用&#…

通过java代码实现对json字符串的格式美化(完整版)

一、前言 之前转载过一篇文章&#xff0c;也是有关于通过java代码实现对json字符串的格式美化&#xff0c;但是那篇文章的实现还不够完善&#xff0c;比如其对字符串中出现特殊字符时&#xff0c;会出现转换失败。因此博主本人也是闲暇时在那份代码的基础上做了完善和补充。好…

[附源码]计算机毕业设计校园租赁系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Tomcat服务器的简介以及安装

文章目录1.概念1.1 什么是Web服务器&#xff1f;1.2 静态资源和动态资源1.3 常用服务器产品2. Tomcat的安装2.1 下载2.2 解压安装2.3 Tomcat的目录结构2.4 Tomcat服务器的启动和关闭2.5 tomcat启动失败的原因2.5.1 查看报错原因2.5.2 错误原因2.5.2.1 查看JAVA_HOME配置是正确2…

ESP32——WEB服务程序移植(基于示例restful_server)

一、简介 将ESP32——WEB服务程序测试项目移植到一个现有项目中&#xff0c;现有项目包括基于固定IP的WIFI连接、OTA升级、Websocket等功能。 二、移植 2.1 参考restful_server项目下分区表文件partitions_example.csv修改项目分区 因模块采用ESP32-WROVER-E(4MB)&#xff…

磨金石教育摄影技能干货分享|乡愁摄影作品欣赏——传统建筑篇

俗话说“一方水土养一方人”&#xff0c;不同的山川地域与气候环境早就不同的地域文化。我国幅员广阔&#xff0c;南北东西跨度极大&#xff0c;因此气候环境与地理环境差异很大。在民俗文化上面也呈现出多元化风格。 这种多元化的文化风格清晰的体现在各地的建筑形式上。重庆的…

Java【String】【StringBuilder】【StringBuffer】你都会用吗

文章目录前言一、常用的方法1、字符串构造2、字符串比较3、字符串查找4、字符串转化5、字符串替换6、字符串分割7、字符串截取二、字符串的不可变性三、StringBuilder、StringBuffer总结前言 在校招和笔试过程中&#xff0c;字符串是相当频繁被问到的话题&#xff0c;在之前的…

iMazing兼容Win和Mac2023免费版iOS设备管理器

iMazing是一款ios设备管理软件&#xff0c;该软件支持对基于iOS系统的设备进行数据传输与备份&#xff0c;用户可以将包括&#xff1a;照片、音乐、铃声、视频、电子书及通讯录等在内的众多信息在Windows/Mac电脑中传输/备份/管理。 软件备份是指将一部手机上的重要信息和资料&…

Pytorch -> ONNX -> TensorRT 模型转换与部署

系统环境&#xff1a; Ubuntu 18.04Cuda 11.3Cudnn 8.4.1 1 、Pytorch -> ONNX 网上相关流程很多&#xff0c;我就不重复了 可以参考Pytorch分类模型转onnx以及onnx模型推理 或者直接看Pytorch官方怎么干的。 ONNX Github onnxruntime调用onnx模型推理时有一个provider…

HTML5期末大作业:基于HTML+CSS+JavaScript茶文化中国水墨风格绿色茶叶销售(5页) 学生网页设计作业源码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

用coding向你最爱的人说圣诞快乐

&#x1f384;&#x1f384;&#x1f384;圣诞节即将到来&#xff0c;今天让我们用编码的方式向你最爱的人表达圣诞节快乐。 圣诞节的起源 圣诞节源自古罗马人迎接新年的农神节&#xff0c;与基督教本无关系。在基督教盛行罗马帝国后&#xff0c;教廷将这种民俗节日纳入基督教体…

DenseNet的基本思想

之前的文章介绍过残差网络的基本思想&#xff1a;残差网络的思想就是将网络学习的映射从X到Y转为学习从X到Y-X的差&#xff0c;然后把学习到的残差信息加到原来的输出上即可。即便在某些极端情况下&#xff0c;这个残差为0&#xff0c;那么网络就是一个X到Y的恒等映射。其示意图…

Java基础类型和运算符

文章目录变量与常量变量的命名规则常量final 关键字修饰的常量字面常量基本类型整型基本整型变量 int长整型 long短整型 short比特型 byte浮点数 float和double关于3*0.10.3三种特殊的double字符型 char布尔类型 boolean类型转换隐式类型提升强制类型转换运算符算数运算符基本四…

vue中打印插件vue-print-nb(二)-实例之两种方法——安包之设置一个id和绑定一个对象 下载print.js之ref设置锚点

vue中打印插件vue-print-nb(二)-实例之两种方法——安包之设置一个id和绑定一个对象 & 下载print.js之ref设置锚点 第一种方法 方式1、设置一个id ① 给要打印的部分设置一个 id ② 在打印按钮中添加 v-print"#id名" 1、安装vue-print-nb插件 npm install v…

Firefly RK3399 PC pro Android 10下载验证

一.Android 源码以及image 1.Android 10代码链接&#xff1a; 百度网盘 请输入提取码 密码&#xff1a;1234 下载后检查md5值&#xff0c;检查下载是否正确&#xff1a; fb41fcdc48b1cf90ecac4a5bb8fafc7a Firefly-RK3399_Android10.0_git_20211222.7z.001 82d665fb54fb412…