【JUC基础】13. 线程池(二)

news2024/11/24 5:03:21

目录

1、前言

2、Java实现线程池

2.1、Executors框架

2.2、newFixedThreadPool

2.3、newCachedThreadPool

2.4、newSingleThreadExecutor

2.5、newScheduledThreadPool

2.5.1、scheduleAtFixedRate

2.5.2、scheduleWithFixedDelay

2.5.3、异常中断

3、execute()和submit()

4、线程池关闭

5、小结


1、前言

我们继续前面的《【JUC基础】12.线程池(一)》。

2、Java实现线程池

2.1、Executors框架

Executors类是Java并发工具包(java.util.concurrent)中提供的一个工具类,用于创建和管理线程池。它提供了一些静态方法,用于创建不同类型的线程池,简化了线程池的创建和配置过程。

Executor框架提供了各种类型的线程池,主要方法有:

/**
 *  固定线程大小的线程池
 */
public static ExecutorService newFixedThreadPool(int nThreads)

/**
 *  单线程的线程池
 */
public static ExecutorService newSingleThreadExecutor() 

/**
 *  可根据实际情况调整线程数量的线程池
 */
public static ExecutorService newCachedThreadPool()

/**
 *  单线程的线程池,扩展了延时和周期性执行的功能
 */
public static ExecutorService newSingleThreadScheduledExecutor()

/**
 *  可执行线程数量的线程池,扩展了延时和周期性执行的功能
 */
public static ExecutorService newScheduledThreadPool(int corePoolSize)

2.2、newFixedThreadPool

newFixedThreadPool()方法。返回一个固定线程数量的线程池。线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲数量,则立即执行。如果没有,则新的任务会被暂存在一个队列中,等到有空闲的线程时,再从任务队列中取出任务执行。

示例代码:

public class FixedThreadPoolTest {

    public static void main(String[] args) {
        // 固定线程数量为3
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 6; i++) {
            executorService.submit(() -> {
                System.out.println("Thread Id:" + Thread.currentThread().getId());
                ThreadUtil.sleep(1000);
            });
        }
    }
}

执行结果如下:我们创建了固定3个线程的线程池,然后我们依次提交6个任务,线程池就会安排这6个任务,然后执行。执行期间我们发现前3个任务和后3个任务的执行时间相差1s,且前3和后3个任务的线程ID是一致的,这就说明线程被分成了2批执行。

2.3、newCachedThreadPool

newCachedThreadPool()方法。返回一个可根据实际情况调整线程数量的线程池。线程池的数量不确定,但如果有空闲线程可以复用,则优先使用可复用线程。如果所有线程都在运行,又有新的任务提交,则会创建新的线程处理任务,处理结束后,线程池回收多余线程。

我们拿2.2示例代码来改造:

public static void main(String[] args) {
    
    // 可调整大小线程池
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        executorService.submit(() -> {
            System.out.println("Thread Id:" + Thread.currentThread().getId() + " is running...");
            ThreadUtil.sleep(1000);
            System.out.println("Thread Id:" + Thread.currentThread().getId() + " done!");
        });
        // 这里多睡2秒,验证是否复用了空闲线程
        if(i == 1){
            ThreadUtil.sleep(2000);
        }
    }
}

运行结果:

当我们i==1的时候睡了2秒。2秒过后,ID为9和10的线程已经执行结束。所以当第二批开始执行的时候,我们看到线程9和10被复用执行了,而与fixedThreadPool不同的是,他自动调整了线程池的线程数量大小,而非固定。因此我们看到了11、12、13......后的线程被创建。

2.4、newSingleThreadExecutor

newSingleThreadExecutor()。返回一个只有一个线程的线程池。若多于1个任务提交到线程池,任务会被存在任务等待队列中,直到当前线程空闲后,再取出执行。

示例代码:

public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        executorService.submit(() -> {
            System.out.println("Thread Id:" + Thread.currentThread().getId() + " is running...");
            ThreadUtil.sleep(1000);
            System.out.println("Thread Id:" + Thread.currentThread().getId() + " done!");
        });
    }
}

执行结果:

2.5、newScheduledThreadPool

newScheduledThreadPool()。可以根据时间需要对线程进行调度的线程池。主要有两个方法:

/**
 * 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后是initialDelay+period 
 * ,然后是initialDelay + 2 * period ,等等。 
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,  TimeUnit unit);

/**
 * 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 
 */
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);

与其他线程池不同,该线程池不一定会立即安排任务执行。更多是起到了定时计划的作用。

2.5.1、scheduleAtFixedRate

使用scheduleAtFixedRate()来调度一个任务。这个任务执行1秒,调度周期是2秒。那么这个任务就会每2秒执行一次。

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.scheduleAtFixedRate(() -> {
        System.out.println(DateUtil.now() + ":Thread Id:" + Thread.currentThread().getId() + " is running...");
        ThreadUtil.sleep(1000);
    // 参数值0表示:立即执行,不延迟
    // 参数值2表示:计划周期为2秒
    }, 0, 2, TimeUnit.SECONDS);
}

执行结果:

当然,scheduleAtFixedRate是不会允许任务堆叠的情况。当一个任务执行时间大于周期时间时,那么周期计划就会等待任务结束。

举个例子:

如周期为2秒,一个任务执行了1秒。那么该计划周期为2秒;

如周期为2秒,一个任务执行了5秒。那么该计划周期会等待任务5秒执行结束,周期就变为5秒;

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.scheduleAtFixedRate(() -> {
        System.out.println(DateUtil.now() + ":Thread Id:" + Thread.currentThread().getId() + " is running...");
        // 这里任务执行改为5秒
        ThreadUtil.sleep(5000);
    }, 0, 2, TimeUnit.SECONDS);
}

执行结果:

2.5.2、scheduleWithFixedDelay

使用scheduleWithFixedDelay()来调度一个任务。这个任务执行1秒,调度周期是2秒。那么这个任务就会每(2+1)秒执行一次。

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.scheduleWithFixedDelay(() -> {
        System.out.println(DateUtil.now() + ":Thread Id:" + Thread.currentThread().getId() + " is running...");
        ThreadUtil.sleep(1000);
    }, 0, 2, TimeUnit.SECONDS);
}

执行结果:

同样,scheduleWithFixedDelay是不会允许任务堆叠的情况。当一个任务执行时间大于周期时间时,那么周期计划就会等待任务结束。

举个例子:

如周期为2秒,一个任务执行了1秒。那么该计划周期为(2+1)秒;

如周期为2秒,一个任务执行了5秒。那么该计划周期会等待任务5秒执行结束,周期就变为(5+2)秒;

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    scheduledExecutorService.scheduleWithFixedDelay(() -> {
        System.out.println(DateUtil.now() + ":Thread Id:" + Thread.currentThread().getId() + " is running...");
        ThreadUtil.sleep(5000);
    }, 0, 2, TimeUnit.SECONDS);
}

执行结果:

2.5.3、异常中断

需要注意的是,如果任务本身抛出异常,那么后续的所有任务都会被中断。

public static void main(String[] args) {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
    AtomicInteger i = new AtomicInteger(0);
    scheduledExecutorService.scheduleWithFixedDelay(() -> {
        System.out.println(DateUtil.now() + ":Thread Id:" + Thread.currentThread().getId() + " is running...");

        if(i.get() == 3){
            // 这里抛个异常
            try {
                int number = 10 / 0;
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }
        }
        i.getAndIncrement();
        ThreadUtil.sleep(1000);
    }, 0, 2, TimeUnit.SECONDS);
}

可以看到后续都不会继续执行:

3、execute()和submit()

ThreadPoolExecutor提供了两种提交任务的方法:submit和execute。

 

execute:将任务提交给线程池进行执行,但无法获取任务的执行结果。适用于不关心任务执行结果的场景。例如,执行一些简单的异步操作或无需返回结果的任务。

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {
    // 执行任务的代码
});

submit:将任务提交给线程池进行执行,并返回一个Future对象,通过该对象可以获取任务的执行状态和结果。适用于需要获取任务执行结果或对任务进行异常处理的场景。

ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(() -> {
    // 执行任务的代码
    return "Task Result";
});

try {
    String result = future.get(); // 获取任务执行结果
    System.out.println("Task Result: " + result);
} catch (InterruptedException | ExecutionException e) {
    // 处理异常
}

4、线程池关闭

前面我们讲了如何创建线程池,线程池类型,以及如何提交任务到线程池中执行。那么当线程池执行完任务,线程处于空闲状态,依旧会占用系统资源。此时我们就需要讲线程池进行关闭,以待垃圾回收器回收。

关闭线程池通常有两种方式:

  • shutdown()方法:调用此方法后,线程池会停止接收新的任务,并尝试将已提交的任务执行完成。已提交但未执行的任务会继续执行,而不会被丢弃。
  • shutdownNow()方法:调用此方法后,线程池会尝试停止所有正在执行的任务,并丢弃所有未执行的任务。该方法会通过中断(interrupt)线程来终止任务的执行。

较为优雅的方式:

此外还可以使用awaitTermination(timeout, unit)方法等待线程池中的任务执行完成。该方法会阻塞当前线程,直到线程池中的任务全部完成或超过指定的超时时间。如果等待超时,调用shutdownNow()方法中断执行中的任务,并尝试终止线程池。最后,调用isTerminated()方法判断线程池是否已经终止,确认所有任务都已完成。

ExecutorService executor = Executors.newFixedThreadPool(5);

// 关闭线程池
executor.shutdown();

try {
    // 等待线程池中的任务执行完成,最多等待5秒
    if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
        // 等待超时,调用shutdownNow()方法终止执行中的任务
        executor.shutdownNow();
        // 再次等待线程池中的任务执行完成,最多等待5秒
        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
            // 等待超时后仍有任务未完成,可能需要其他处理方式
        }
    }
} catch (InterruptedException e) {
    // 捕获中断异常,可能需要其他处理方式
} finally {
    // 判断线程池是否已终止
    if (executor.isTerminated()) {
        // 线程池已终止,进行相关资源的释放
    }
}

通过以上步骤,可以保证线程池能够优雅地终止,并确保所有任务都得到执行或被中断。这样可以避免应用程序中出现线程池资源泄漏或未处理的任务。

5、小结

到此为止,线程池相关的基本知识都介绍完了。当然这些只是线程池的一些基本用法以及常规使用。面对基础入门也是足够了。至于类似线程池的源码,自定义扩展线程池等,放到后面看看进阶篇再写吧~

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

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

相关文章

大数据挖掘企业服务平台(TipDM大数据挖掘建模平台)-快速构建数据挖掘工程

“TipDM大数据挖掘建模平台”&#xff08;以下简称平台&#xff09;是由广东泰迪智能科技股份有限公司自主研发&#xff0c;基于Python引擎的数据挖掘建模平台。使用平台配置的开箱即用的算法组件&#xff0c;用户可在没有编程基础的情况下&#xff0c;通过拖拽的方式进行操作&…

蓝牙规范系列--经典蓝牙概述(第一篇)

一、目的 从本篇开始介绍经典蓝牙的基础知识&#xff0c;内容较多故会分成多篇进行介绍。 经典蓝牙&#xff08;BR/EBR&#xff09;射频&#xff08;物理层PHY&#xff09;工作在免授权的2.4G ISM频段&#xff08;2400 - 2483.5 MHz&#xff09;&#xff0c;使用跳频技术来对抗…

基于java的班级管理系统的设计与实现

一:需求分析 1.功能需求 1).能够实现对班级学生基本资料的录入,包括学生的学号,姓名,性别,所学专业,家庭住址以及出生年月等。 2).能够实现对学生基本资料的修改。 3).根据学号对学生资料进行查询。 4).能够删除某些学生的资料。 二:总体设计 本班级管理系统共有6…

静态Web服务器搭建

文章目录 一&#xff0c;安装Apache软件&#xff08;一&#xff09;Apache软件安装&#xff08;二&#xff09;Apache软件管理&#xff08;三&#xff09;Apache软件基本设置&#xff08;四&#xff09;测试Apache服务器 二&#xff0c;Apache服务器的配置文件&#xff08;一&a…

【C++】结构体 - 定义和使用,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,结构体 const

文章目录 1. 定义和使用2. 结构体数组3. 结构体指针4. 结构体嵌套结构体5. 结构体做函数参数6. 结构体 const 1. 定义和使用 结构体属于用户自定义的数据类型&#xff0c;允许用户存储不同的数据类型。 struct 结构体 {结构体成员列表}; 通过结构体创建变量的方法有三种&…

2023,智能硬件的AIGC“又一春”

​ 文|智能相对论 作者|佘凯文 消费电子产品风光不再&#xff0c;特别是自去年以来&#xff0c;电子消费市场经历了一整年的寒潮袭击&#xff0c;智能手机等产品达到10年消费谷底&#xff0c;PC出货量整体下降16%&#xff0c;不仅如此&#xff0c;包括平板、可穿戴设备也一改…

URP Shader FrameBuffer Fetch Mali Crash

1&#xff09;URP Shader FrameBuffer Fetch Mali Crash ​2&#xff09;Unity模型Lightmap UV相关的疑问 3&#xff09;动画上下半身融合问题 4&#xff09;AnimatorControllerPlayable.PrepareFrame函数在什么情况下调用 这是第338篇UWA技术知识分享的推送&#xff0c;精选了…

如何将 HTML 字符串转换成 DOM 对象:用 DOMParser

如何将 HTML 字符串转换成 DOM 对象&#xff1a;用 DOMParser 一、问题描述 有的时候我们需要处理一些 HTML 字符串&#xff0c;比如我需要从下方 HTML 字符串中提取每个 <a> 标签的内容和属性。 <pre><a href"cc1245.jpg">cc1245.jpg</a>…

将ipa文件上架苹果应用商店详细教程

使用windows电脑打包好uniapp的ios应用之后&#xff0c;还有一个麻烦事&#xff0c;就是需要将这个打包好的ipa格式的文件&#xff0c;上架到苹果的应用商店。用户才能安装。 而苹果提供的上传工具&#xff0c;比如xcode或transports&#xff0c;只能安装在mac电脑&#xff0c…

国产AIGC大模型汇总

“ 随着ChatGPT和GPT-4的出现&#xff0c;直接引爆了全球的AIGC大模型市场&#xff01;为了赶上这一波热潮&#xff0c;国内的大厂和创业公司也纷纷内卷起来&#xff0c;相继发布了自己的大模型。但是到目前为止&#xff0c;没有一个大模型能与ChatGPT相提并论&#xff0c;更比…

【算法系列之二叉树III】leetcode236. 二叉树的最近公共祖先

617.合并二叉树 力扣题目链接 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新二叉树。合并的规则是&…

MySQL 三万字精华总结 + 面试100 问,和面试官扯皮绰绰有余

MySQL 三万字精华总结 面试100 问&#xff0c;和面试官扯皮绰绰有余 写在之前&#xff1a;不建议那种上来就是各种面试题罗列&#xff0c;然后背书式的去记忆&#xff0c;对技术的提升帮助很小&#xff0c;对正经面试也没什么帮助&#xff0c;有点东西的面试官深挖下就懵逼了。…

linuxOPS基础_linux文本文件查看

vi/vim vim文档编辑操作太多了,可以看这篇单独介绍vim的文章>https://blog.csdn.net/weixin_44368963/article/details/130963920 cat查看文件 命令&#xff1a;cat 作用&#xff1a;查看文件内容 语法&#xff1a;#cat 文件名称 ​ #cat 文件1 文件2 > 文件3 **特别注…

如何利用CiteSpace快速锁定领域内最新研究热点并制作精美的可视化专题图?

【基于Citespace和vosviewer文献计量学相关论文 】 ​ 01 文献计量学方法与应用 1. 文献计量学方法基本介绍 2. 与其他综述方法区别联系 3. 各学科领域应用趋势近况 4. 主流分析软件优缺点对比 5. 经典高分10SCI思路复盘 6. 软件安装与Java环境配置 02 主题确定、数据检…

Vue+springboot校园跳蚤二手市场管理系统

摘 要 本毕业设计的内容是设计并且实现一个基于Springboot框架的校园跳蚤市场管理系统。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。校园跳蚤市场管理系统的功能已基本实现&#xff0c;主要包括用户、卖家、商品分类…

中国存储竞争新格局:曙光掌舵分布式存储市场

近日&#xff0c;赛迪顾问发布了《中国分布式存储市场研究报告&#xff08;2023&#xff09;》。 作为数字经济的底座&#xff0c;数据存储的重要性日益凸显。 近年来&#xff0c;凭借高性能、高可靠性、高可扩展性等优势&#xff0c;基于分布式架构的分布式存储迎来了蓬勃发…

基于SpringBoot+vue的火车订票管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

动静态库的区别

(5条消息) linux-动态库制作与使用_云的小站的博客-CSDN博客 (5条消息) linux-静态库制作与使用_云的小站的博客-CSDN博客 目录 编译区别 使用不同编译的可执行程序。 推荐使用动态链接 动态链接具有以下优缺点 编译区别 动态库&#xff1a;代码不加载到可执行程序中&am…

Java:Mybatis-Plus自动填充功能配置和使用

Mybatis-Plus可以实现字段自动填充功能 文档 https://baomidou.com/pages/4c6bcf/ 目录 需求数据库设置默认值通过代码的方式进行自动填充配置自动填充设置方式一设置方式二 测试依赖pom.xml 需求 我们需要自动填充的字段&#xff1a; 插入数据时自动填充&#xff1a;creat…

view的常用属性和方法介绍(arcgis for javascript)

ArcGIS for JavaScript中的视图&#xff08;view&#xff09;是一个地图实例类&#xff0c;用于管理地图的显示区域、符号和标注等。通过视图类&#xff0c;可以实现以下功能&#xff1a; 显示地图&#xff1a;将地图显示在Web页面上。 缩放&#xff1a;缩放视图到指定的级别。…