java 线程池的使用_Java 使用线程池执行若干任务
在执行一系列带有IO操作(例如下载文件),且互不相关的异步任务时,采用多线程可以很极大的提高运行效率。线程池包含了一系列的线程,并且可以管理这些线程。例如:创建线程,销毁线程等。本文将介绍如何使用Java中的线程池执行任务。
1 任务类型
在使用线程池执行任务之前,我们弄清楚什么任务可以被线程池调用。按照任务是否有返回值可以将任务分为两种,分别是实现Runnable的任务类(无参数无返回值)和实现Callable接口的任务类(无参数有返回值)。在打代码时根据需求选择对应的任务类型。
1.1 实现Runnable接口的类
多线程任务类型,首先自然想到的就是实现 Runnable 接口的类,Runnable接口提供了一个抽象方法run,这个方法无参数,无返回值。例如:
Runnable task = newRunnable() {
@Overridepublic voidrun() {
System.out.println("Execute task.");
}
};
或者Java 8 及以上版本更简单的写法:
Runnable task = ()->{
System.out.println("Execute task.");
};
1.2 实现Callable接口的类
于Runnable一样Callable也只有一个抽象方法,不过该抽象方法有返回值。在实现该接口的时候需要制定返回值的类型。例如:
Callable callableTask = ()-> "finished";
2 线程池类型
java.util.concurrent.Executors 提供了一系列静态方法来创建各种线程池。下面例举出了主要的一些线程池及特性,其它未例举线程池的特性可由下面这些推导出来。
2.1 线程数固定的线程池 Fixed Thread Pool
顾名思义,这种类型线程池线程数量是固定的。如果线程数量设置为n,则任何时刻该线程池最多只有n个线程处于运行状态。当线程池中处于饱和运行状态时,再往线程池中提交的任务会被放到执行队列中。如果线程池处于不饱和状态,线程池也会一直存在,直到ExecuteService 的shutdown方法被调用,线程池才会被清除。
//创建线程数量为5的线程池。
ExecutorService executorService = Executors.newFixedThreadPool(5);
2.2 可缓存的线程池 Cached Thread Pool
这种类型的线程池初始大小为0个线程,随着往池里不断提交任务,如果线程池里面没有闲置线程(0个线程也表示没有闲置线程),则会创建新的线程,保证没有任务在等待;如果有闲置线程,则复用闲置状态线程执行任务。处于闲置状态的线程只会在线程池中缓存60秒,闲置时间达到60s的线程会被关闭并移出线程池。在处理大量短暂的(官方说法:short-lived)异步任务时可以显著得提供程序性能。
//创建一个可缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
2.3 单线程池
这或许不能叫线程池了,由于它里面的线程永远只有1个,而且自始至终都只有1个(为什么说这句话,因为要和 Executors.newFixedThreadPool(1) 区别开来),所以还是叫它“单线程池把”。你尽可以往单线程池中添加任务,但是每次只执行1个,且任务是按顺序执行的。如果前面的任务出现了异常,当前线程会被销毁,但1个新的线程会被创建用来执行后面的任务。以上这些和线程数只有1个的线程Fixed Thread Pool一样。两者唯一不同的是, Executors.newFixedThreadPool(1)可以在运行时修改它里面的线程数,而 Executors.newSingleThreadExecutor() 永远只能有1个线程。至于“为什么”,我准备专门再写一篇博客通过源代码来分析。
//创建一个单线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
2.4 工作窃取线程池
扒开源码,会发现工作窃取线程池本质是 ForkJoinPool ,这类线程池充分利用CPU多核处理任务,适合处理消耗CPU资源多的任务。它的线程数不固定,维护的任务队列有多个,当一个任务队列完成时,相应的线程会从其它的任务队列中窃取任务执行,这也意味着任务的开始执行顺序并和提交顺序相同。如果有更高的需求,可以直接通过ForkJoinPool获取线程池。
//创建一个工作窃取线程池,使用CPU核数等于机器的CPU核数
ExecutorService executorService =Executors.newWorkStealingPool();//创建一个工作窃取线程池,使用CPU 3 个核进行计算,工作窃取线程池不能设置线程数
ExecutorService executorService2 = Executors.newWorkStealingPool(3);
2.5 计划任务线程池
计划任务线程池可以按计划执行某些任务,例如:周期性的执行某项任务。
//获取一个大小为2的计划任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);//添加一个打印当前线程信息计划任务,该任务在3秒后执行
scheduledExecutorService.schedule(() -> { System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS);//添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每5秒执行一次。如果任务执行时间超过了5秒,则下一次将会在前一次执行完成之后立即执行
scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);//添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每次在任务执行之后5秒执行下一次。
scheduledExecutorService.scheduleWithFixedDelay(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);//逐个清除 idle 状态的线程
scheduledExecutorService.shutdown();//阻塞,在线程池被关调之前代码不再往下走
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3 使用线程池执行任务
前面提到,任务类型分为有返回值和无返回值的类型,这里的调用也分为有返回值调用和无返回值的调用。
3.1 无返回值任务的调用
如果是无返回值任务的调用,可以用execute或者submit方法,这种情况下二者本质上一样。为了于有返回值任务调用保持统一,建议采用submit方法。
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);//提交一个无返回值的任务(实现了Runnable接口)
executorService.submit(()->System.out.println("Hello"));
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
如果有一个任务集合,可以一个个提交。
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List tasks =Arrays.asList(
()->System.out.println("Hello"),
()->System.out.println("World"));//逐个提交任务
tasks.forEach(executorService::submit);
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3.2 有返回值任务的调用
有返回值的任务需要实现Callable接口,实现的时候在泛型位置指定返回值类型。在调用submit方法时会返回一个Future对象,通过Future的方法get()可以拿到返回值。这里需要注意的是,调用get()时代码会阻塞,直到任务完成,有返回值。
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future future = executorService.submit(()->"Hello");
System.out.println(future.isDone());//false
String value =future.get();
System.out.println(future.isDone());//true
System.out.println(value);//Hello
如果要提交一批任务,ExecutorService除了可以逐个提交之外,还可以调用invokeAll一次性提交,invokeAll的内部实现其实就是用一个循环逐个提交任务。invokeAll返回的值是一个Future List。
ExecutorService executorService = Executors.newFixedThreadPool(2);
List> tasks = Arrays.asList(()->"Hello", ()->"World");
List> futures = executorService.invokeAll(tasks);
invokeAny方法也很有用,线程池执行若干个实现了Callable的任务,然后返回最先执行结束的任务的值,其它未完成的任务将被正常取消掉不会有异常。如下代码不会输出“Hello”
ExecutorService executorService = Executors.newFixedThreadPool(2);
List> tasks =Arrays.asList(
()->{
Thread.sleep(500L);
System.out.println("Hello");return "Hello";
}, ()->{
System.out.println("World");return "World";
});
String s=executorService.invokeAny(tasks);
System.out.println(s);//World
输出:
World
World
另外,在查看ExecutorService源码时发现它还提供了一个方法 Future submit(Runnable task, T result); ,可以通过这个方法提交一个实现了Runnable接口的任务,然后有返回值,而Runnable接口中的run方法时没有返回值的。那它的返回值是哪来的呢?其实问题在于该submit方法后面的一个参数,这个参数值就是返回的值。调用submit方法之后,有一通操作,然后直接把result参数返回了。
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future future = executorService.submit(() -> System.out.println("Hello"), "World");
System.out.println(future.get());//输出:World
4 小结
在利用多线程处理任务时,应该根据情况选择合适的任务类型和线程池类型。如果无返回值,可以采用实现Runnable或Callable接口的任务;如果有返回值,应该使用实现Callable接口的任务,返回值通过Future的get方法取到。选用线程池时,如果只用1个线程,用单线程池或者容量为1的固定容量线程池;处理大量short-live任务是,使用可缓存的线程池;若要有计划或者循环执行某些任务,可以采用计划任务线程池;如果任务需要消耗大量的CPU资源,应用工作窃取线程池。
java 线程池的使用_Java 使用线程池执行若干任务相关推荐
- java中线程池的使用_Java中线程池的简单使用
什么是线程池? 顾名思义线程池就是线程的容器 举个例子:在没有共享电源的年代,车站有5个人手机都没电且都没有带电源,这五个人想要给手机充电只能去车站的售货亭各花100块钱买一个移动电源:但是现在共享电 ...
- java讲对象放在常量池的方法_java的常量池里面都放了些神马东西
展开全部 理解Java常量池 JVM运行时数据区的内e69da5e6ba9062616964757a686964616f31333264656233存模型由五部分组成: [1]方法区 [2]堆 [3] ...
- java中的僵死进程_Java中线程间怎么通讯?什么叫僵死线程?
<尸家保镖> <猛鬼出千> <不死心灵> <大家发财> <灵幻少女> <九天玄女> <僵尸至尊> <湘西尸王& ...
- java实现线程同步的方法_Java实现线程同步方法及原理详解
一.概述 无论是什么语言,在多线程编程中,常常会遇到多个线同时操作程某个变量(读/写),如果读/写不同步,则会造成不符合预期的结果. 例如:线程A和线程B并发运行,都操作变量X,若线程A对变量X进行赋 ...
- java什么时候需要同步_JAVA中线程在什么时候需要同步和互斥
JAVA中线程在什么时候需要同步和互斥 关注:265 答案:6 mip版 解决时间 2021-01-27 08:10 提问者时光易老 2021-01-27 03:32 JAVA中线程在什么时候需要 ...
- java线程安全的方法_Java实现线程安全的方式
多线程环境中如何保证线程安全?java可以实现线程安全的方式归纳如下: 1.使用synchronized关键字 synchronized关键字可以修饰方法和代码块,它的语义是保证同一段代码同一时间只能 ...
- java线程等待都完成_Java等待线程完成
我有一个线程下载数据,我想等待,直到下载完成之前,我加载数据.有这样做的标准方法吗? 更多信息: 我有一个下载类,从URL(序列化POJO)获取数据.下载是Runnable和Observable.它跟 ...
- java的dbcp使用方法_Java dbcp连接池的使用方法
Java dbcp连接池的使用方法 发布时间:2020-10-31 00:59:26 来源:亿速云 阅读:89 作者:Leah 这篇文章将为大家详细讲解有关Java dbcp连接池的使用方法,文章内容 ...
- java list 多条件排序_java – 如何对arraylist执行一系列排序操作(多个排序条件)
Collections.sort(myList,new Comparator() { @Override public int compare(Object o1,Object o2) { // wr ...
最新文章
- R语言使用lmPerm包应用于线性模型的置换方法(置换检验、permutation tests)、使用lm模型构建简单线性回归模型、使用lmp函数生成置换检验回归分析模型
- 使用Aspose.Cells的基础知识整理
- linux系统如何安装其他包,Linux系统下如何安装软件包
- 遗传算法求解极大值问题
- 【MFC】自绘对话框动画效果的状态栏
- Kubernetes 将何去何从?
- php rpoplpush,Redis Rpoplpush 命令
- Java从键盘获取两个输入值并计算和
- android安卓 通知notification
- python抽取html中的链接
- QT 实现百万级的数据显示内存消耗几十兆
- 作为米粉,我不得不说一说小米11全系,小米还是最初的小米呀
- 关于前后台部署打包时需要修改的ip位置
- (JAVA编程练习):输入某年某月某日,判断这一天是这一年的第几天?
- 李嘉诚能否再续神话?“长科版”上市内幕
- SLF4J: The requested version 1.5.8 by your slf4j binding is not compatible with [1.6] SLF4J: See htt
- 【Mo&AI TIME 人工智能技术博客】矛与盾的对决——神经网络后门攻防
- springboot-EasyExcel
- 不仅仅是一把瑞士军刀 —— Apifox的野望和不足
- 你的OA,坚持应用了吗?
热门文章
- linux init.d 密码,Linux基础之init.d、rc.local
- 小米10pro使用说明书_30W有线无线快充,小米的这款立式无线充电宝表现不错
- 手机平板巡检系统,掀起设备巡检的第2次革命
- linux 安装mysql 5.7.16
- 认识div(division)在排版中的作用
- 关于 react的生命周期
- Apache RewriteCond %{REQUEST_FILENAME} 不起作用问题
- nginx配置注意事项1
- 谈谈这些年来我为什么一直在坚持
- html商城加减号,商城购物车的加减号控制商品数量