作者:廖祥俐
大多数并发应用程序都是围绕着“任务执行 ”进行构造的:任务通常是一些抽象的且离散的工作单元,通过把应用程序的工作分解到多个任务中。在理想状态下,各个任务之间是相互独立的:任务并不依赖于其他任务的状态,结果或边界效应,独立性有助于实现并发。
采用多线程往往基于以下几个情境:
然而,在使用多线程的时候,显示的为每一个任务分配一个线程有一定的缺陷,尤其当需要创建大量线程的时候,主要有以下几方面:
针对此,java.util.concurrent包提供了一种灵活线程管理框架:Executor框架,任务执行的主要抽象不是Thread,而是Executor。
public interface Executor{
void execute(Runnable command);
}
Java从1.5版本开始,为简化多线程并发编程,引入全新的并发编程包:java.util.concurrent及其并发编程框架(Executor框架)。 Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
ExecutorService:在ScheduledExecutorService基础上,添加了一些用于管理生命周期的方法接口,同时还有一些用于任务提交的方法接口
AbstractExecutorService:提供 ExecutorService 执行方法的默认实现,此类使用包中提供的默认 FutureTask 类实现了 submit、invokeAny 和 invokeAll 方法
ScheduledExecutorService:在ExecutorService的基础上,ScheduledExecutorService提供了按时间安排执行任务的功能,它提供的方法主要有:
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,long period,TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit);
}
ThreadPoolExecutor(继承AbstractExecutorService):ThreadPoolExcutor为一些Executor提供了基本的实现,这些Executor是由Executors中的工厂 newCahceThreadPool、newFixedThreadPool和newScheduledThreadExecutor返回的
ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor类实现了ScheduledExecutorService接口中定义的以不同方法执行任务的方法
JVM只有在所有(非守护)线程全部终止后才会退出,在使用Executor的时候,需要考虑如何正确关闭Executor,不然会导致JVM无法结束。
由于Executor是异步地执行任务,因此在任何时间,之前提交的任务状态都不是立即可见的,有些任务已经完成,有些正在运行,而还有些在等待队列中等待执行。
在关闭应用时,可能采取最平缓的关闭方式(完成所有已经启动的任务,并且不再接受新的任务),也有可能是其它方式,在关闭的时候,希望Executor能够记录受到影响的任务,并可以返回相应的状态。
为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了一些用于管理生命周期的方法(同时还有一些用于任务提交的方法)
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
……
}
一个Executor的生命周期有三种状态运行、关闭和终止。
Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再向Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException, 这里主要是当Executor处于关闭状态时,任务会被转交到RejectedExecutionHandler去执行,RejectedExecutionHandler使得execute方法抛出RejectedExecutionException异常
Executors中通过静态工厂方法提供了4种基本的线程池创建方法:
1,newCachedThreadPool
缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中
-缓存型池子通常用于执行一些生存期很短的异步型任务,因此在一些面向连接的daemon型SERVER中用得不多。
-能reuse的线程,必须是timeout IDLE(Timeout waiting for idle object)内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
2,newFixedThreadPool
-newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
-其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
-和cacheThreadPool不同,FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
-从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同: fixed池线程数固定,并且是0秒IDLE(无IDLE) cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE
3,ScheduledThreadPool
-调度型线程池
-这个池子里的线程可以按schedule依次delay执行,或周期执行
4,SingleThreadExecutor
-单例线程,任意时间池中只能有一个线程
-用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)
1,类的可变状态时至关重要的:所有的并发问题都可以归纳为如何协调对可变状态的访问,可变状态越少,越容易保证线程的安全性;
2,尽量将将类的域声明为final类型,除非它们需要是可变的,这样有利于线程安全(不可变对象一定是线程安全的)
3,使用Executor优先于线程:涉及到线程或线程池相关,均需要考虑如何优雅的中止线程(需要精细的代码),尽可能通过Excutor框架来管理线程的执行。
4,newCachedThreadPool适合有大量短生命周期的线程场景,在生产环境中一般采用newFixedThreadPool固定线程池大小。
Java并发编程 - Executor,Executors,ExecutorService, CompletionServie,Future,Callable
《Effective Java》
《Java并发编程实战》