通过Executor来启动线程比使用Thread的start方法更好,除了
更易管理
效率更好(用线程池实现,节约开销)
关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中
ExecutorService的生命周期包括三种状态:运行、关闭、终止。
shutdown()->状态关闭->
ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完后->终止状态
4种方法ThreadFactory建立的线程
newCachedThreadPool() :
缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中;
缓存型池子通常用于执行一些生存期很短的异步型任务,
因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是Executor的首选;
能reuse的线程,必须是timeout IDLE内的池中线程,缺省 timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池;
注意,超过TIMEOUT不活动,其会自动被终止
newFixedThreadPool(int):
newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程;
独特:
任意时间点,最多只能有固定数目的活动线程存在,
此时如果有新的线程要建立,只能放在另外的队列中等待
,
直到当前的线程中某个线程终止直接被移出池子;
FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器;
从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:
fixed池线程数固定,并且是0秒IDLE(无IDLE)
fixed池线程数固定,并且是0秒IDLE(无IDLE)
cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE
newScheduledThreadPool(int):
调度型线程池;
这个池子里的线程可以按schedule依次delay执行,或周期执行;
SingleThreadExecutor():
任意时间池中只能有一个线程
用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)
实际运用中:
一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool
Executor执行Runnable任务
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
public
class
TestCachedThreadPool{
public
static
void
main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// ExecutorService executorService = Executors.newSingleThreadExecutor();
for
(
int
i =
0
; i <
5
; i++){
executorService.execute(
new
TestRunnable());
System.out.println(
"************* a"
+ i +
" *************"
);
}
executorService.shutdown();
}
}
class
TestRunnable
implements
Runnable{
public
void
run(){
System.out.println(Thread.currentThread().getName() +
"线程被调用了。"
);
}
}
pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务
Executor执行Callable任务
import
java.util.ArrayList;
import
java.util.List;
import
java.util.concurrent.*;
public
class
CallableDemo{
public
static
void
main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList =
new
ArrayList<Future<String>>();
//创建10个任务并执行
for
(
int
i =
0
; i <
10
; i++){
//使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
Future<String> future = executorService.submit(
new
TaskWithResult(i));
//将任务执行结果存储到List中
resultList.add(future);
}
//遍历任务的结果
for
(Future<String> fs : resultList){
try
{
while
(!fs.isDone);
//Future返回如果没有完成,则一直循环等待,直到Future返回完成
System.out.println(fs.get());
//打印各个线程(任务)执行的结果
}
catch
(InterruptedException e){
e.printStackTrace();
}
catch
(ExecutionException e){
e.printStackTrace();
}
finally
{
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
executorService.shutdown();
}
}
}
}
class
TaskWithResult
implements
Callable<String>{
private
int
id;
public
TaskWithResult(
int
id){
this
.id = id;
}
/**
* 任务的具体过程,一旦任务传给ExecutorService的submit方法,
* 则该方法自动在一个线程上执行
*/
public
String call()
throws
Exception {
System.out.println(
"call()方法被自动调用!!! "
+ Thread.currentThread().getName());
//该返回结果将被Future的get方法得到
return
"call()方法被自动调用,任务返回的结果是:"
+ id +
" "
+ Thread.currentThread().getName();
}
}
,如果没有,才会创建新的线程来执行任务。
另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待
,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回
public ThreadPoolExecutor (
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
)
corePoolSize:线程池中所保存的核心线程数,包括空闲线程。
maximumPoolSize:池中允许的最大线程数。
keepAliveTime:线程池中的空闲线程所能持续的最长时间。
unit:持续时间的单位。
workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。
总结:当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池中的线程数量是否大于maximumPoolSize。另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
public
static
ExecutorService newCachedThreadPool() {
return
new
ThreadPoolExecutor(
0
, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new
SynchronousQueue<Runnable>());
}
corePoolSize:0
maximumPoolSize:
Integer.MAX_VALUE
keepAliveTime:
60L
由于核心线程数为0,因此每次添加任务,都会先从线程池中找空闲线程,
如果没有就会创建一个线程来执行新的任务,并将该线程加入到线程池中
public
static
ExecutorService newFixedThreadPool(
int
nThreads) {
return
new
ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new
LinkedBlockingQueue<Runnable>());
}
corePoolSize:
nThreads
maximumPoolSize:
nThreads
keepAliveTime:
0L
线程池的大小的固定,不会动态地扩大;
另外,keepAliveTime设定为了0,也就是说线程只要空闲下来,就会被移除线程池
排队的策略:
直接提交 newCachedThreadPool
SynchronousQueue:将任务直接交给线程处理,如果线程都在工作,则构造新线程处理任务,
要求无界
maximumPoolSizes(Integer.MAX_VALUE) 以避免拒绝新提交的任务
无界队列 newFixedThreadPool
(
采用预定义容量的 LinkedBlockingQueue,理论上是该缓冲队列可以对无限多的任务排队
)
当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列
有界队列
当使用有限的 maximumPoolSizes 时,有界队列(一般缓冲队列使用ArrayBlockingQueue,并制定队列的最大长度)有助于防止资源耗尽,
但是可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设定合理的参数