当前位置: 首页 > news >正文

JAVA线程池原理详解二

JAVA线程池原理详解二

一. Executor框架

Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务的线程相当于消费者,并用Runnable来表示任务,Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。

Executor的UML图:(常用的几个接口和子类)
在这里插入图片描述
Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command)。

ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法。

AbstractExecutorService:ExecutorService执行方法的默认实现。

ScheduledExecutorService:一个可定时调度任务的接口。

ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池。

ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。

在前面介绍的JAVA线程既是工作单元,也是执行机制。而在Executor框架中,我们将工作单元与执行机制分离开来。Runnable和Callable是工作单元(也就是俗称的任务),而执行机制由Executor来提供。这样一来Executor是基于生产者消费者模式的,提交任务的操作相当于生成者,执行任务的线程相当于消费者。

1、从类图上看,Executor接口是异步任务执行框架的基础,该框架能够支持多种不同类型的任务执行策略。

public interface Executor {void execute(Runnable command);
}

Executor接口就提供了一个执行方法,任务是Runnbale类型,不支持Callable类型。
2、ExecutorService接口实现了Executor接口,主要提供了关闭线程池和submit方法:

public interface ExecutorService extends Executor {List<Runnable> shutdownNow();boolean isTerminated();<T> Future<T> submit(Callable<T> task);}

另外该接口有两个重要的实现类:ThreadPoolExecutor与ScheduledThreadPoolExecutor。
其中ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务;而ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行任务,或者定期执行命令。

在上一篇文章中,我是使用ThreadPoolExecutor来通过给定不同的参数从而创建自己所需的线程池,但是在后面的工作中不建议这种方式,推荐使用Exectuors工厂方法来创建线程池

这里先来区别线程池和线程组(ThreadGroup与ThreadPoolExecutor)这两个概念:
a、线程组就表示一个线程的集合。

b、线程池是为线程的生命周期开销问题和资源不足问题提供解决方案,主要是用来管理线程。

Executors可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadExecutor和CachedThreadPool

a、SingleThreadExecutor:单线程线程池

ExecutorService threadPool = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

我们从源码来看可以知道,单线程线程池的创建也是通过ThreadPoolExecutor,里面的核心线程数和线程数都是1,并且工作队列使用的是无界队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。

b、FixedThreadExecutor:固定大小线程池

ExecutorService threadPool = Executors.newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

这个与单线程类似,只是创建了固定大小的线程数量。

c、CachedThreadPool:无界线程池

ExecutorService threadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

无界线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。

Callable、Future、FutureTash详解

Callable与Future是在JAVA的后续版本中引入进来的,Callable类似于Runnable接口,实现Callable接口的类与实现Runnable的类都是可以被线程执行的任务。

三者之间的关系:

Callable是Runnable封装的异步运算任务。

Future用来保存Callable异步运算的结果

FutureTask封装Future的实体类

1、Callable与Runnbale的区别

a、Callable定义的方法是call,而Runnable定义的方法是run。

b、call方法有返回值,而run方法是没有返回值的。

c、call方法可以抛出异常,而run方法不能抛出异常。

2、Future
Future表示异步计算的结果,提供了以下方法,主要是判断任务是否完成、中断任务、获取任务执行结果

public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

3、FutureTask<V>
可取消的异步计算,此类提供了对Future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则阻塞get方法。

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>

FutureTask不仅实现了Future接口,还实现了Runnable接口,所以不仅可以将FutureTask当成一个任务交给Executor来执行,还可以通过Thread来创建一个线程。

Callable与FutureTask

定义一个callable的任务:

public class MyCallableTask implements Callable<Integer>{@Overridepublic Integer call() throws Exception{System.out.println("callable do something");Thread.sleep(100);return new Random().nextInt(100);}}
public class CallableTest
{public static void main(String[] args) throws Exception{Callable<Integer> callable = new MyCallableTask();FutureTask<Integer> future = new FutureTask<Integer>(callable);Thread thread = new Thread(future);thread.start();Thread.sleep(100);//尝试取消对此任务的执行future.cancel(true);//判断是否在任务正常完成前取消System.out.println("future is cancel:" + future.isCancelled());if(!future.isCancelled()){System.out.println("future is cancelled");}//判断任务是否已完成System.out.println("future is done:" + future.isDone());if(!future.isDone()){System.out.println("future get=" + future.get());}else{//任务已完成System.out.println("task is done");}}
}

执行结果:

callable do somothing
future is cancel:true
future is done:true
task is done

这个DEMO主要是通过调用FutureTask的状态设置的方法,演示了状态的变迁。

a、第11行,尝试取消对任务的执行,该方法如果由于任务已完成、已取消则返回false,如果能够取消还未完成的任务,则返回true,该DEMO中由于任务还在休眠状态,所以可以取消成功。

future.cancel(true);

b、第13行,判断任务取消是否成功:如果在任务正常完成前将其取消,则返回true

System.out.println("future is cancel:" + future.isCancelled());

c、第19行,判断任务是否完成:如果任务完成,则返回true,以下几种情况都属于任务完成:正常终止、异常或者取消而完成。 我们的DEMO中,任务是由于取消而导致完成。

System.out.println("future get=" + future.get());

d、在第22行,获取异步线程执行的结果,我这个DEMO中没有执行到这里,需要注意的是,future.get方法会阻塞当前线程, 直到任务执行完成返回结果为止。

System.out.println("future get=" + future.get());

Callable与Future

public class CallableThread implements Callable<String>private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
{@Overridepublic String call()throws Exception{System.out.println("进入Call开始休眠。休眠时间:" + df.format(new Date(System.currentTimeMillis())));Thread.sleep(10000);return "睡觉啦";}public static void main(String[] args) throws Exception{Callable<String> callable = new CallableThread();ExecutorService executor = Executors.newSingleThreadExecutor();Future<String> submit = executor.submit(callable);executor.shutdown();Thread.sleep(5000);System.out.println("主线程休眠5秒。当前时间:" + df.format(new Date(System.currentTimeMillis())) );String s = submit.get();System.out.println("Future数据已拿到,str=" + s + ",当前时间:" + df.format(new Date(System.currentTimeMillis())));}
}

执行结果:

进入Call开始休眠。休眠时间:2023-02-28 17:09:17:629
主线程休眠5秒。当前时间:2023-02-28 17:09:22:631
Future数据已拿到,str=睡觉啦,当前时间:2023-02-28 17:09:27:645

这里的future是直接扔到线程池里面去执行的。由于要打印任务的执行结果,所以从执行结果来看,主线程虽然休眠了5s,但是从Call方法执行到拿到任务的结果,这中间的时间差正好是10s,说明get方法会阻塞当前线程直到任务完成。

通过FutureTask也可以达到同样的效果:
public static void main(String[] args) throws Exception{ExecutorService es = Executors.newSingleThreadExecutor();Callable<String> call = new CallableThread();FutureTask<String> task = new FutureTask<String>(call);es.submit(task);es.shutdown();Thread.sleep(5000);System.out.println("主线程休眠5秒。当前时间:" + df.format(new Date(System.currentTimeMillis())) );String s = submit.get();System.out.println("Future数据已拿到,str=" + s + ",当前时间:" + df.format(new Date(System.currentTimeMillis())));}

以上的组合可以给我们带来这样的一些变化:

如有一种场景中,方法A返回一个数据需要10s,A方法后面的代码运行需要20s,但是这20s的执行过程中,只有后面10s依赖于方法A执行的结果。如果与以往一样采用同步的方式,势必会有10s的时间被浪费,如果采用前面两种组合,则效率会提高:

1、先把A方法的内容放到Callable实现类的call()方法中

2、在主线程中通过线程池执行A任务

3、执行后面方法中10秒不依赖方法A运行结果的代码

4、获取方法A的运行结果,执行后面方法中10秒依赖方法A运行结果的代码

这样代码执行效率一下子就提高了,程序不必卡在A方法处。

相关文章:

JAVA线程池原理详解二

JAVA线程池原理详解二 一. Executor框架 Eexecutor作为灵活且强大的异步执行框架&#xff0c;其支持多种不同类型的任务执行策略&#xff0c;提供了一种标准的方法将任务的提交过程和执行过程解耦开发&#xff0c;基于生产者-消费者模式&#xff0c;其提交任务的线程相当于生…...

Java 常用 API

文章目录一、Math二、System三、Object1. toString() 方法2. equals() 方法四、Arrays1. 冒泡排序2. Arrays 常用方法五、基本类型包装类1. Integer2. int 和 String 相互转换3. 字符串中数据排序4. 自动装箱和拆箱六、日期类1. Date2. SimpleDateFormat3. Calendar4. 二月天一…...

记一次分布式环境下TOKEN实现用户登录

背景&#xff1a; ​ 以前的单体项目&#xff0c;使用的是session来保存用户登录状态&#xff0c;控制用户的登录过期时间等信息&#xff0c;但是这个session是只保存在该服务器的这个系统内存中。系统只有一个服务就没关系&#xff0c;但是如果是分布式的服务&#xff0c;每个…...

用cpolar发布本地的论坛网站 1

网页论坛向来是个很神奇的地方&#xff0c;曾经的天涯论坛和各种BBS&#xff0c;大家聚在在一起讨论某个问题&#xff0c;也能通过论坛发布想法&#xff0c;各种思维碰撞在一起&#xff0c;发生很多有趣的故事&#xff0c;也产生了很多流传一时的流行语录。当然&#xff0c;如果…...

CSS的4种引入方式

CSS的4种引入方式 目录CSS的4种引入方式一、内嵌式&#xff1a;CSS写在style标签中二、外联式&#xff1a;CSS写在一个单独的.css文件中三、行内式&#xff1a;CSS写在标签的style属性中四、导入外部样式五、css引用的优先级六、link和import的区别一、内嵌式&#xff1a;CSS写…...

Shell高级——Linux中的文件描述符(本质是数组的下标)

以下内容源于C语言中文网的学习与整理&#xff0c;非原创&#xff0c;如有侵权请告知删除。 前言 Linux中一切接文件&#xff0c;比如 C 源文件、视频文件、Shell脚本、可执行文件等&#xff0c;就连键盘、显示器、鼠标等硬件设备也都是文件。 一个 Linux 进程可以打开成百上…...

Nvidia jetson nano硬件架构

资料来源 官方文档中心 https://developer.nvidia.com/embedded/downloads -> 选jetson -> Jetson Nano Product Design Guide //产品设计指导(入口) //-> 1.1 References 列出了相关的文档 -> Jetson Nano Developer Kit Carrier Board Specification //板子标注…...

ffmpeg多路同时推流

一、ffmpeg常见使用方法1.1利用FFMPEG命令进行文件分割1.2转换格式1.3推流配置方法一&#xff1a;ngnix&#xff08;不推荐&#xff0c;推流不好使&#xff09;方法二&#xff1a;srs&#xff08;强烈推荐&#xff09;1.4查看nginx启动是否成功二、ffmpeg推流——>ngnix单路…...

一次性搞定 `SHOW SLAVE STATUS` 的解读

一次性搞定 SHOW SLAVE STATUS 的解读 解析日志文件的位置 诚然, GTID(全局事务标识符)已经在 MySQL 5.6中得到支持, 此外,还可以通过 Tungsten replicator 软件来实现(2009年以后一直有谷歌在维护,不是吗?)。 但有一部分人还在使用MySQL 5.5的标准副本方式, 那么这些二进制日…...

【代码随想录训练营】【Day25】第七章|回溯算法 |216.组合总和III|17.电话号码的字母组合

组合总和III 题目详细&#xff1a;LeetCode.216 做过上一题组合后&#xff0c;再来写这道题就显得得心应手了&#xff0c;通过理解回溯算法的模版&#xff0c;也总结出了算法中的一些特点&#xff1a; 回溯算法与递归算法类似&#xff0c;同样需要参数、结束条件和主体逻辑回…...

docker使用

https://blog.csdn.net/u012563853/article/details/125295985http://www.ppmy.cn/news/11249.html启动 docker服务并设置开机自动启动dockersudo systemctl start docker sudo systemctl enable dockerdocker 常见启动失败问题:https://blog.csdn.net/zhulianseu/article/deta…...

手把手docker registry配置登录名/密码

我们的Docker私有仓库Registry服务只有加了认证机制之后我们的Registry服务才会更加的安全可靠。赶快跟随以下步骤来增加认证机制吧。 创建docker registry工作目录 mkdir -p /data/docker.registry 创建将保存凭据的文件夹 mkdir -p /data/docker.registry/etc/registry/auth…...

一步打通多渠道服务场景 中电金信源启移动开发平台MADP功能“上新”

日前&#xff0c;中电金信源启移动开发平台MADP功能迭代升级&#xff0c;“上新”源启小程序开发平台。定位“为金融业定制”的移动PaaS平台&#xff0c;源启小程序开发平台为银行、互联网金融、保险、证券客户提供一站式小程序的开发、运营、营销全生命周期管理技术支撑&#…...

Kubernetes06:Controller (Deployment无状态应用)

Kubernetes06:Controller 1、什么是controller 管理和运行容器的对象&#xff0c;是一个物理概念 在集群上管理和运行容器的对象 2、Pod和Controller之间的关系 Pod是通过controller来实现应用的运维 比如伸缩、滚动升级等等操作Pod和Controller之间通过 label 标签建立关系…...

低代码开发平台选型必看指南

低代码开发是近年来逐渐兴起的一种新型软件开发方式。它通过封装常见的软件开发流程和代码&#xff0c;使得非专业的开发者也能够轻松创建复杂的应用程序。这种开发方式已经受到了许多企业的青睐&#xff0c;成为提高生产效率、降低开发成本的一种有效途径。 低代码开发的核心…...

OVN:ovn20.03.1/ovs2.13.0编译rpm过程

操作系统openeuler22.0&#xff0c;x86架构分别下载ovn和ovs的源码https://github.com/openvswitch/ovs/tree/v2.13.0https://github.com/ovn-org/ovn/tree/v20.03.1安装必要工具&#xff1a;yum install -y unzip tar make autoconf automake libtool rpm-build gcc libuuid-d…...

Shell管道

一、管道是什么 英文是pipe。 把一个命令的标准输出作为下一个命令的标准输入&#xff0c;以这种方式连接的两个或者多个命令就形成了管道 使用竖线|连接多个命令&#xff0c;称为管道符。 语法格式如下&#xff1a; command1 | command2 [ | commandN... ] command1的标准…...

Zynq UltraScale系列使用MIPI CSI-2 RX Subsystem 解码MIPI视频PD输出 提供2套工程源码和技术支持

目录1、前言2、设计思路和架构3、vivado工程详解4、上板调试验证5、福利&#xff1a;工程代码的获取1、前言 本设计采用OV5640摄像头MIPI模式作为输入&#xff0c;分辨率为1280x72060Hz&#xff0c;MIPI解码方案采用Xilinx官方提供的MIPI CSI-2 RX Subsystem IP解码MIPI视频&a…...

C++:详解C++11 线程休眠函数

休眠函数简介1: 让线程休眠一段时间1.1&#xff1a;std::chrono 的时钟 clock简介 C11 之前并未提供专门的休眠函数&#xff0c;C语言的 sleep、usleep函数其实是系统提供的函数&#xff0c;不同的系统函数的功能还要些差异。 在Windows系统中&#xff0c;sleep的参数是毫秒 …...

TryHackMe-The Great Escape(Docker)

The Great Escape 我们的开发人员创建了一个很棒的新网站。你能冲出沙盒吗&#xff1f; 端口扫描 循例 nmap Web信息收集 robots.txt: /exif-util是文件上传点&#xff0c;但是绕过之后貌似没啥用 在robots.txt当中披露了可能存在.bak.txt&#xff0c;现在我们已知的文件就是…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...