java多线程开发,Executors、FutureTask、Callable
java多线程如何应用呢,几乎学java的同学都知道Thread类和Runable接口。继承Thread类或者实现Runable接口,调用thread的start方法即可启动线程。
然后是线程池,就是启动一系列的线程,当需要启动某个线程时,从线程池中拿取一个线程。
最近使用到需要启动一个线程进行复杂运算并且得到其返回值。
就用到Callable。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable 接口是从jdk1.5之后才有的,其使用call方法代替run方法,相较Runable:可以有返回值,也可以抛出异常,这两点是引入Callable的主要原因
Callable应该如何用才会有返回值呢:先有个例子 在来一一讲解;
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyTask());
String result = null;
try {
result= (String) future.get(3, TimeUnit.MINUTES);
} catch (TimeoutException e) {
log.error("TimeoutException!");
} catch (InterruptedException e) {
log.error("InterruptedException:" + e.getMessage());
} catch (ExecutionException e) {
log.error("ExecutionException:" + e.getMessage());
}
(一).Executors的用法
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
我们使用了newSingleThreadExecutor 来创建一个单线程的线程池,翻一下源码看里面做了什么事
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ok,创建了一个代理线程池来处理单线程的问题,其中扔进去一个只有一个线程并且最大值为1的线程池。
代理线程池先不用说,其实里面调用的也是完全的ThreadPoolExeutor的方法,先来解析一下ThreadPoolExeutor的参数:
ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
.
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize-池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
ThreadPoolExecutor是Executors类的底层实现。
(二).executor.submit
既然用到了submit方法,就看一看submit里面做了什么事情:
public <T> Future<T> submit(Callable<T> task) {
return e.submit(task);
}
//这个是DelegatedExecutorService 的方法,这个类是FinalizableDelegatedExecutorService的父类,
//看到他其实是调用ThreadPoolExecutor的submit方法,其参数为Callable类型。T为返回值类型
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//这是AbstractExecutorService类中的方法,其是ThreadPoolExecutor的父类,同时实现了ExecutorService接口
于是乎,FutureTask实现了Runable接口,所以实现Callable的过程其实就是启动了一个Runable来执行Callable,以方便更好的控制。
void innerRun() {
if (!compareAndSetState(0, RUNNING))
return;
try {
runner = Thread.currentThread();
if (getState() == RUNNING) // recheck after setting thread
innerSet(callable.call());
else
releaseShared(0); // cancel
} catch (Throwable ex) {
innerSetException(ex);
}
}
//这个是FutureTask中的run()的实现,在这里它讲call()方法的返回值赋值给了result,并且吃掉了异常,至于为什么我们后面说
FutureTask是实现了Future接口的,Future接口主要有这几种方法来控制其Callable任务:
A、boolean cancel(Boolean mayInterruptlfRunning):试图取消该Future里关联的Callable任务
B、V get():返回Callable任务里的call方法的返回值,调用该方法将导致线程阻塞,必须等到子线程结束才得到返回值
C、V get(long timeout, TimeUnit unit):返回Callable任务里的call方法的返回值,该方法让程序最多阻塞timeout和unit指定的时间。
如果经过指定时间后Callable任务依然没有返回值,将会抛出TimeoutException。
D、boolean isCancelled:如果在Callable任务正常完成前被取消,则返回true。
E、boolean isDone:如果Callable任务已经完成,则返回true
其主要是get()方法,即获取线程的返回值的方法。这个是阻塞的,即当调用get的时候会等待线程结束才能有返回,否则就一直等待,或者等待超过超时时间。
还记得run的时候吃掉了异常么,那里吃掉的异常将会在这里抛出来:所以,如果不调用get()方法,则执行的call方法是不抛出异常的,也没有返回值,即跟普通的Runable一样的。
//这个是get的主要实现方法
V innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException {
if (!tryAcquireSharedNanos(0, nanosTimeout))
throw new TimeoutException();
if (getState() == CANCELLED)
throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
return result;
}
(三):ExecutorService还提供了一些其他的方法,如中断线程。。
(四):摘抄一些
下面介绍一下几个类的源码:
ExecutorService newFixedThreadPool (int nThreads):固定大小线程池。
可以看到,corePoolSize和maximumPoolSize的大小是一样的(实际上,后面会介绍,如果使用无界queue的话maximumPoolSize参数是没有意义的),keepAliveTime和unit的设值表名什么?-就是该实现不想keep alive!最后的BlockingQueue选择了LinkedBlockingQueue,该queue有一个特点,他是无界的。
1. public static ExecutorService newFixedThreadPool(int nThreads) { 2. return new ThreadPoolExecutor(nThreads, nThreads, 3. 0L, TimeUnit.MILLISECONDS, 4. new LinkedBlockingQueue<Runnable>()); 5. } |
ExecutorService newSingleThreadExecutor():单线程
1. public static ExecutorService newSingleThreadExecutor() { 2. return new FinalizableDelegatedExecutorService 3. (new ThreadPoolExecutor(1, 1, 4. 0L, TimeUnit.MILLISECONDS, 5. new LinkedBlockingQueue<Runnable>())); 6. } |
ExecutorService newCachedThreadPool():无界线程池,可以进行自动线程回收
这个实现就有意思了。首先是无界的线程池,所以我们可以发现maximumPoolSize为big big。其次BlockingQueue的选择上使用SynchronousQueue。可能对于该BlockingQueue有些陌生,简单说:该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。
1. public static ExecutorService newCachedThreadPool() { 2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3. 60L, TimeUnit.SECONDS, 4. new SynchronousQueue<Runnable>());
|
先从BlockingQueue<Runnable> workQueue这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。
所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
queue上的三种类型。
排队有三种通用策略:
直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量
java多线程开发,Executors、FutureTask、Callable的更多相关文章
- Java多线程开发技巧
很多开发者谈到Java多线程开发,仅仅停留在new Thread(...).start()或直接使用Executor框架这个层面,对于线程的管理和控制却不够深入,通过读<Java并发编程实践&g ...
- java多线程创建-Thread,Runnable,callable和threadpool
java创建多线程的方式有许多种,这里简要做个梳理 1. 继承Thread类 继承java.lang.Thread类,创建本地多线程的类,重载run()方法,调用Thread的方法启动线程.示例代码如 ...
- Java 多线程Future和FutureTask
Future表示一个任务的周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务. Future接口源码: public interface Future<V> ...
- Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread
事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一 ...
- Java多线程开发系列之四:玩转多线程(线程的控制2)
在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join().守护线程.本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠.让步.优先级.挂起和恢复.停止等. 废话不多说,我们直接 ...
- Java多线程开发系列之一:走进多线程
对编程语言的基础知识:分支.选择.循环.面向对象等基本概念理解后,我们需要对java高级编程有一定的学习,这里不可避免的要接触到多线程开发. 由于多线程开发整体的系统比较大,我会写一个系列的文章总结介 ...
- java多线程获取返回结果--Callable和Future示例
package test.guyezhai.thread; import java.util.ArrayList; import java.util.Date; import java.util.Li ...
- java多线程之 Executors线程池管理
1. 类 Executors 此类中提供的一些方法有: 1.1 public static ExecutorService newCachedThreadPool() 创建一个可根据需要创建新线程的线 ...
- Java多线程开发系列之二:如何创建多线程
前文已介绍过多线程的基本知识了,比如什么是多线程,什么又是进程,为什么要使用多线程等等. 在了解了软件开发中使用多线程的基本常识后,我们今天来聊聊如何简单的使用多线程. 在Java中创建多线程的方式有 ...
随机推荐
- 第几天——第九届蓝桥杯C语言B组(省赛)第一题
原创 标题:第几天 2000年的1月1日,是那一年的第1天. 那么,2000年的5月4日,是那一年的第几天? 注意:需要提交的是一个整数,不要填写任何多余内容. 这题是送分题,只需要注意一下2000年 ...
- EF 查询视图返回重复数据的问题
在特殊的情况下查询过滤视图 会出现重复的数据结果集(返回的多条数据结果一致). 原因是啥:主键 在数据库设计的理念中:每个表都应该的唯一的主键.但视图不同,EF中会自动按视图的最前几个非空型字段设置为 ...
- javascript实现playfair和hill密码算法
时至期末,补习信息安全概论作业.恰巧遇古典密码学算法中的playfair算法和hill算法,用javascript语言实现起来是在有趣,边查百度边编码,顺便好好补习一下javascript基础. pl ...
- 当在安卓低版本呈现的界面(H5)出现问题的时候,我们怎么解决?
昨天,在医院现场的客服人员,向我们反馈一个问题:说一位用户用他的安卓手机打开我们的app之后,界面是乱掉的:如下图: 向客服询问了具体的设备信息:安卓系统版本号是4.2 下意识觉得是因为css的兼容问 ...
- python之爬虫--番外篇(一)进程,线程的初步了解
整理这番外篇的原因是希望能够让爬虫的朋友更加理解这块内容,因为爬虫爬取数据可能很简单,但是如何高效持久的爬,利用进程,线程,以及异步IO,其实很多人和我一样,故整理此系列番外篇 一.进程 程序并不能单 ...
- Mysql内置功能《四》存储过程
存储过程 一 存储过程介绍 存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的一堆sql 使用存储过程的优点: #1. 用于替代程序写的SQL语句,实 ...
- bzoj 4598: [Sdoi2016]模式字符串
题目描述 给出n个结点的树结构T,其中每一个结点上有一个字符,这里我们所说的字符只考虑大写字母A到Z,再给出长度为m的模式串s,其中每一位仍然是A到z的大写字母. Alice希望知道,有多少对结点&l ...
- UITableViewCell笔记
默认的四种cell的类型 原网站 还有这个带图的 据我自己试验,只有value2不自带imageview 不同的accessoryType 可以看到,一个tabelviewcell的contentvi ...
- 高德地图API获取天气
1.建立行政区规划清单表 use edw; drop table if exists dim_prov_city_adcode; create table if not exists dim_prov ...
- Thread类和Runnable接口的比较
Thread和Runnable的联系 Thread类的定义: public class Thread extends Object implements Runnable 联系:从Thread类的定义 ...