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中创建多线程的方式有 ...
随机推荐
- 通过python实现wc基本功能
---恢复内容开始--- 1.Github项目地址: https://github.com/zhg1998/ww/blob/master/wc.py 2.项目相关要求: 写一个命令行程序,模仿已有wc ...
- 浅谈HTTPS以及Fiddler抓取HTTPS协议(摘抄)
一.浅谈HTTPS 我们都知道HTTP并非是安全传输,在HTTPS基础上使用SSL协议进行加密构成的HTTPS协议是相对安全的.目前越来越多的企业选择使用HTTPS协议与用户进行通信,如百度.谷歌等. ...
- [LeetCode 题解]: Longest Substring Without Repeating Characters
Given a string, find the length of the longest substring without repeating characters. For example, ...
- 简单几步,提升.Net Core的开发效率
附加IIS进程调式? 以前在开发ASP.NET(MVC)项目的时候,为了加快程序的启动速度(调式),我们会选择使用IIS.先用IIS架设还在开发的项目,在需要调式的时候附加进程,而在更多时候,如果调整 ...
- node.js的总结-可以应付bat的社招面试
什么是NodeJS Node.js采用模块化结构,按照CommonJS规范定义和使用模块.模块与文件是一一对应关系,即加载一个模块,实际上就是加载对应的一个模块文件. JS是脚本语言,脚本语言都需要一 ...
- ResourceBundle和Locale
一.认识国际化资源文件 这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以: 轻松地本地化或翻译成不同的语言 一次处理多个语言环境 ...
- iOS tableview性能优化及分析
1.最常用的就是cell的重用, 注册重用标识符 每次滑动cell时需要先去缓存池中寻找可循环利用的cell,如果没有则再重新创建cell 2.减少cell中控件的数量 view对象尽量缩减控件的数量 ...
- POJ3321 Apple Tree (JAVA)
树形数组题,有一定难度. 首先得搞清楚树形数组是什么 - 它是建立在原始数组上的统计数组 - 目的:方便对原始数组进行切片统计,主要用于统计切片的累加和 其实你可以对切片进行扫描,把元素一个一个加起来 ...
- 条目二十八《正确理解由reverse_iterator的base()成员函数所产生的iterator的用法》
条目二十八<正确理解由reverse_iterator的base()成员函数所产生的iterator的用法> 迭代器的种类一共有四种,上面已经说过了.这里就不再次写出来. 这一个条目主要是 ...
- js 正则表达式(reg)
一.RegExp对象方法: 1.exec() 检索字符串中指定的值,并返回值(找不到返回null) 效果: <textarea name="content" id=&qu ...