Java并发编程(六)线程池[Executor体系]

news2024/11/25 18:38:38

概述

在处理大量任务时,重复利用线程可以提高程序执行效率,因此线程池应运而生。

  • 它是一种重用线程的机制,可以有效降低内存资源消耗
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
  • 线程池可以帮助我们更好地管理线程的生命周期和资源使用,避免线程频繁地创建和销毁带来的性能问题

同时,线程池还可以提供一些额外的功能,例如线程池的大小控制、线程池的任务队列、线程池的拒绝策略等。线程池中通常维护一个线程队列,线程队列中保存着已创建的线程,当有新的任务需要执行时,线程池中的线程就可以从队列中取出一个线程来执行任务,任务执行完毕后线程可以被放回线程队列中,等待下一个任务的到来

核心执行流程

线程池执行流程
  • 当有新任务需要线程执行时,线程池会先判断是否有空闲的核心线程,如果有则将任务分配给其中一个空闲的核心线程执行。如果没有空闲的核心线程或者核心线程的数量还没达到最大值,则创建一个新的核心线程来执行任务
  • 若核心线程已经达到最大值且工作队列未满,则将新提交的任务存储在这个工作队列中。如果工作队列已满,则会判断线程池中的线程数是否已经达到最大值,如果没有则创建一个新的非核心线程来执行任务
  • 若工作队列已满且线程池中的线程都已经处于工作状态,即核心线程和非核心线程都在执行任务,则交给饱和策略来处理这个任务。饱和策略可以决定如何处理无法处理的任务,例如抛出异常或者阻塞任务提交

线程池状态

  • RUNNING:该状态代表能接受新任务以及处理任务(初始状态)
  • SHUTDOWN:该状态代表不接受新任务,但处理已添加的任务(调用shutdown()时,由RUNNING->SHUTDOWN)
  • STOP:该状态时表示不接受新任务,不处理已添加任务,并会中断正在处理中的任务(调用shutdownNow()时,由RUNNING或者SHUTDOWN→STOP)
  • TIDYING:进入SHUTDOWN或者STOP状态后,所有任务都被处理或者清理干净后就会进入该状态,同时会执行terminated()方法(该方法是个钩子函数,自定义实现)
  • TERMINATED:结束状态,执行完terminated方法后由TIDYING->TERMINATED

Executor 框架

两级调度模型 

在HotSpotVM的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程
Java线程启动时会创建一个本地操作系统线程;当该 Java 线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU

  • 在上层架构中,Java多线程程序通常把应用分解为若干个任务,应用程序通过Executor框架将这些任务映射为固定数量的线程
  • 在下层架构中,操作系统内核将这些线程映射到硬件处理器上,下层的调度不受应用程序的控制

Executor结构

Executor框架包含的主要的类与接口如图所示

  • Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来
  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务
  • ScheduledThreadPoolExecutor是一个实现类,用来延迟之后执行任务或者定时执行任务。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大
  • 实现Future接口的FutureTask类,代表异步计算的结果
  • Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行

ThreadPoolExecutor

构造函数详细说明

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory,
						  RejectedExecutionHandler handler) {
	if (corePoolSize < 0 ||
		maximumPoolSize <= 0 ||
		maximumPoolSize < corePoolSize ||
		keepAliveTime < 0)
		throw new IllegalArgumentException();
	if (workQueue == null || threadFactory == null || handler == null)
		throw new NullPointerException();
	this.acc = System.getSecurityManager() == null ?
			null :
			AccessController.getContext();
	this.corePoolSize = corePoolSize;
	this.maximumPoolSize = maximumPoolSize;
	this.workQueue = workQueue;
	this.keepAliveTime = unit.toNanos(keepAliveTime);
	this.threadFactory = threadFactory;
	this.handler = handler;
}

参数介绍:

  • corePoolSize:核心线程池个大小。一般来说任务比较耗时可以配CPU核数*2,因为这样可以充分利用CPU,任务小并且执行很快则可以配CPU核数+1或者更小(因为线程上下文切换耗时)(获取CPU核心数:Runtime.getRuntime().availableProcessors())
  • maximumPoolSize:最大线程池大小。当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务,总线程数≤maximumPoolSize
  • keepAliveTime:空闲时间,超过核心线程数的线程在到达空闲时间后会被销毁
  • TimeUnit : 时间单位
  • BlockingQueue:用来暂时保存任务的队列(阻塞队列)
  • ThreadFactory:自定义的线程工厂,默认是一个新的、非守护线程并且不包含特殊的配置信息,我们也可以自定义加入我们的调试信息,比如线程名称、错误日志等
  • RejectedExecutionHandler:饱和策略。当线程数=maximumPoolSize,且任务队列已满时,多余的任务需要采取的措施,有以下几种(默认AbortPolicy):
    • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
    • DiscardPolicy: 丢掉这个任务并且不会有任何异常
    • DiscardOldestPolicy:丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
    • CallerRunsPolicy:主线程会自己去执行该任务,不会等待线程池中的线程去执行
    • 自定义:当然也可以自定义策略

常用ThreadPoolExecutor类型

ThreadPoolExecutor通常使用工厂类Executors来创建,包括3种ThreadPoolExecutor类型:

  • FixedThreadPool:可重用固定线程数的线程池
  • SingleThreadExecutor:单个线程的线程池(只有一个工作线程)
  • CachedThreadPool:根据需要创建新线程的线程池
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,
								  0L, TimeUnit.MILLISECONDS,
								  new LinkedBlockingQueue<Runnable>());
}
  • 其中corePool和maximumPoolSize 都被设置成指定的参数
  • keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止
  • 适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,更适合于负载比较重的服务器
fixedThreadPool执行流程
  • 如果当前线程少于corePool,则创建新线程来执行当前任务
  • 当运行的线程数等于corePool之后,将任务加入LinkedBlockingQueue队列中
  • 线程执行完当前任务后会循环反复从LinkedBlockingQueue获取任务来执行

由于为LinkedBlockingQueue无界队列(长度Integer.MAX_VALUE),所以会出现如下情景:

  • maximumPoolSize和keepAliveTime参数将会无效,因为maximumPoolSize=corePool
  • 不会拒绝任务,因为是无界队列,任务不会满
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1, 1,
								0L, TimeUnit.MILLISECONDS,
								new LinkedBlockingQueue<Runnable>()));
}

corePool和maximumPoolSize 均被设置成了1,其他影响和运行方式都与FixedThreadPool相同

CachedThreadPool
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
								  60L, TimeUnit.SECONDS,
								  new SynchronousQueue<Runnable>());
}
  • corePoolSize核心线程数为0,而maximumPoolSize为Integer.MAX_VALUE(21亿多),意味着没有空闲线程就会不断的创建线程去执行,极端情况会耗尽CPU和内存资源;
  • keepAliveTime=60s后空闲线程会被终止,所以长时间内保持空闲的情况下不会占用任何资源
  • SynchronousQueue是没有容量的阻塞队列,每个插入操作都会等待另一个线程对应的取出操作
  • 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器

ScheduleThreadPoolExecutor

构造函数

public ScheduledThreadPoolExecutor(int corePoolSize,
								   RejectedExecutionHandler handler) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue(), handler);
}

ScheduledThreadPoolExecutor为了实现周期性任务对ThreadPoolExecutor做了如下修改:

  • 使用DelayedWorkQueue作为任务队列
  • 获取任务的方式不同,同样都是队列的take,但增加了时间的判断
  • 执行周期任务后,增加了额外的处理(需要把任务重新添加进队列)

常用ScheduleThreadPoolExecutor类型

ScheduledThreadPoolExecutor通常使用工厂类Executors来创建,2种类型:

  • ScheduledThreadPoolExecutor: 包含若干个线程 ScheduledThreadPoolExecutor
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 
    • ScheduledThreadPoolExecutor 适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景

  • SingleThreadScheduledExecutor: 只包含一个线程 ScheduledThreadPoolExecutor
    public static ScheduledExecutorService newSingleThreadScheduledExecutor()
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
    • SingleThreadScheduledExecutor 适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景

FutureTask

Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或Callable接口的实现类提交(submit)给 ThreadPoolExecutor或ScheduledThreadPoolExecutor时,ThreadPoolExecutor或ScheduledThreadPoolExecutor会向我们返回一个FutureTask对象

Executor 实践

ThreadPoolExecutor

package com.bierce;
import java.util.concurrent.*;
public class TestThreadPoolExecutor {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //方式一:Runnable方式的分配10个任务提交给线程池
        ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
        for (int i = 0; i <= 10; i++) {
            executorService.submit(threadPoolDemo);
        }
        //方式二:Callable方式的分配10个任务提交给线程池
        for (int i = 0; i <= 10; i++) {
            Future<Object> sum = executorService.submit(() -> {
                int sum1 = 0;
                for (int i1 = 1; i1 <= 100; i1++) {
                    sum1 += i1;
                }
                return sum1;
            });
            System.out.println(Thread.currentThread().getName() + ":" + sum.get()); //main:5050
        }
        //关闭线程池
        executorService.shutdown();
    }
}
class ThreadPoolDemo implements Runnable{
    private int i = 0;
    @Override
    public void run() {
        while (i<10){
            System.out.println(Thread.currentThread().getName() + ":" + i++);
        }
    }
}

ScheduleThreadPoolExecutor

package com.bierce;
import java.util.Random;
import java.util.concurrent.*;
public class TestScheduleThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        for (int i = 1; i <=5 ; i++) {
            //调用schedule方法执行任务
            Future<Integer> random = scheduledExecutorService.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int random = new Random().nextInt(100);
                    System.out.println(Thread.currentThread().getName() + ":" + random);
                    return random;
                }
            },1,TimeUnit.SECONDS); //每隔一秒执行一个任务
            System.out.println(random.get());
        }
        scheduledExecutorService.shutdown(); //关闭线程池
    }
}

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

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

相关文章

Jmeter-压力测试工具

文章目录 Jmeter快速入门1.1.下载1.2.解压1.3.运行 2.快速入门2.1.设置中文语言2.2.基本用法 Jmeter快速入门 1s内发送大量请求&#xff0c;模拟高QPS&#xff0c;用以测试网站能承受的压力有多大 Jmeter依赖于JDK&#xff0c;所以必须确保当前计算机上已经安装了JDK&#xff0…

DAY2,ARM(特殊功能寄存器,数据操作指令,跳转指令)

1.cmp、sub、b指令的使用&#xff1b; 代码&#xff1a; .text .global _start _start:mov r0,#9mov r1,#15b funfun:cmp r0,r1beq stopsubcc r1,r1,r0subhi r0,r0,r1b funstop:b stop .end结果&#xff1a; 2.汇编指令计算1~100之间和&#xff1b; 代码&#xff1a; .text …

echart 词云图

const graphOut ref(null); const optionGraph reactive({series: [{type: graph,shape: circle,symbolSize: [215, 50],//散点形状设置symbol: circle’, ‘rect’, ‘roundRect’, ‘triangle’, ‘diamond’, ‘pin’, arrow’symbol: rect,layout: force,force: {repulsio…

Docker安装elasticsearch分布式搜索

文章目录 ☀️安装elasticsearch☀️1.部署单点es&#x1f338;1.1.创建网络&#x1f338;1.2.下载镜像&#x1f338;1.3.运行 ☀️2.部署kibana&#x1f338;2.1.部署&#x1f338;2.2.DevTools ☀️3.安装IK分词器&#x1f338;3.1.在线安装ik插件&#xff08;较慢&#xff0…

逗号操作符

逗号表达式&#xff0c;就是用逗号隔开的多个表达式。 逗号表达式&#xff0c;从左向右依次执行。整个表达式的结果是最后一个表达式的结果。 运用&#xff1a;

【字典学习+稀疏编码Sparse Encoding】简单介绍与sklearn的实现方式

文章目录 1、字典学习与稀疏编码2、sklearn的实现3、示例 1、字典学习与稀疏编码 简单来说&#xff0c;稀疏编码就是把输入向量&#xff08;信号&#xff09;/ 矩阵&#xff08;图像&#xff09;表示为稀疏的系数向量和一组超完备基向量&#xff08;字典&#xff09;的线性组合…

BeanFactory与Applicationcontext(1)

BeanFactory是接口&#xff0c;提供了IOC容器最基本的形式&#xff0c;给具体的IOC容器的实现提供了规范。BeanFactory是spring的“心脏”&#xff0c;核心容器&#xff0c;它也是Applicationcontext的父接口。 BeanFactory实质上并未提供过多的方法&#xff0c;spring容器的I…

Mr. Cappuccino的第63杯咖啡——Spring之AnnotationConfigApplicationContext源码分析

Spring之AnnotationConfigApplicationContext源码分析 源码分析 源码分析 以上一篇文章《Spring之Bean的生命周期》的代码进行源码分析 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(SpringConfig02.class); LifeCycleBe…

通达信接口调用过程需要借助什么?

通达信接口是一种用于获取、传输和处理股票市场相关数据的软件接口&#xff0c;以提供了一种连接股票市场数据源和数据使用者之间的通道&#xff0c;允许开发者通过编程方式获取股票行情数据、交易数据和相关信息等。如果调用通达信接口&#xff0c;需要借助以下几个方面的工具…

9-AJAX-1入门

AJAX 目录 AJAX 概念和 axios 使用认识 URLURL 查询参数常用请求方法和数据提交HTTP协议-报文接口文档案例 - 用户登录form-serialize 插件 01.AJAX 概念和 axios 使用 目标 了解 AJAX 概念并掌握 axios 库基本使用 讲解 什么是 AJAX ? mdn 使用浏览器的 XMLHttpRequest…

前后端分离------后端创建笔记(06)新增接口页面布局

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

[PyTorch][chapter 50][创建自己的数据集 2]

前言&#xff1a; 这里主要针对图像数据进行预处理.定义了一个 class Pokemon(Dataset) 类&#xff0c;实现 图像数据集加载,划分的基本方法. 目录&#xff1a; 整体框架 __init__ load_images save_csv divide_data __len__ denormalize __g…

[NepCTF 2023] crypto 复现

这个赛很不理想&#xff0c;啥都不会。 拿了WP看了几个题&#xff0c;记录一下 random_RSA 这题不会是正常情况&#xff0c;我认为。对于论文题&#xff0c;不知道就是不知道&#xff0c;基本没有可能自己去完成论文。 题目不长&#xff0c;只有两个菜单&#xff0c;共可交…

Springboot 在 redis 中使用 BloomFilter 布隆过滤器机制

一、导入SpringBoot依赖 在pom.xml文件中&#xff0c;引入Spring Boot和Redis相关依赖 <!-- Google Guava 使用google的guava布隆过滤器实现--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><vers…

IPv4分组

4.3.1 IPv4分组 IP协议定义数据传送的基本单元——IP分组及其确切的数据格式 1. IPv4分组的格式 IPv4分组由首部和数据部分&#xff08;TCP、UDP段&#xff09;组成&#xff0c;其中首部分为固定部分&#xff08;20字节&#xff09;和可选字段&#xff08;长度可变&#xff0…

行业追踪,2023-08-14

自动复盘 2023-08-14 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

深入浅出 栈和队列(附加循环队列、双端队列)

栈和队列 一、栈 概念与特性二、Stack 集合类及模拟实现1、Java集合中的 Stack2、Stack 模拟实现 三、栈、虚拟机栈、栈帧有什么区别&#xff1f;四、队列 概念与特性五、Queue集合类及模拟实现1、Queue的底层结构&#xff08;1&#xff09;顺序结构&#xff08;2&#xff09;链…

做海外游戏推广有哪些条件?

做海外游戏推广需要充分准备和一系列条件的支持。以下是一些关键条件&#xff1a; 市场调研和策略制定&#xff1a;了解目标市场的文化、玩家偏好、竞争格局等是必要的。根据调研结果制定适合的推广策略。 本地化&#xff1a;将游戏内容、界面、语言、货币等进行本地化&#…

计算两个字符串之间的编辑距离【支持多字节字符串】

/*** 计算两个字符串之间的编辑距离【支持多字节字符串】** param string $str1 求编辑距离中的其中一个字符串* param string $str2 求编辑距离中的另一个字符串** return int*/ function levenshtein_copy(string $str1, string $str2): int {$arr1 mb_str_split($str1);$ar…

IK分词器升级,MySQL热更新助一臂之力

ik分词器采用MySQL热更新 ​ 官方所给的IK分词器只支持远程文本文件热更新&#xff0c;不支持采用MySQL热更新&#xff0c;没关系&#xff0c;这难不倒伟大的博主&#xff0c;给哈哈哈。今天就来和大家讲一下如何采用MySQL做热更新IK分词器的词库。 一、建立数据库表 CREATE…