概述
前面所说的都是通过创建线程的方法使程序并行运行,一般的做法是,创建一个Runnable对象,然后封装为Thread对象,通过start方法启动线程,并在线程运行过程中通过sleep、interrupt等方法来控制线程的运行。可见,在之前的程序书写中,线程的创建、运行、休眠和终止都是手动完成的。 如果创建线程较少,这种手动操作并不繁琐,但如果创建线程过多,这种手动操作就显得有些繁琐。
在一个最多支持四个线程同时运行的多核处理器上,如果创建的线程数少,则多核处理器的处理能力没有被充分利用,如果创建的线程数过多,则机器的资源不够,多余的线程只能以时间片轮转的方式运行,或者等待。可见,线程创建的过多或过少都不好,一般情况下,是让线程数等于多核处理器可以同时处理的线程数。
创建一个新线程的代价很高,尤其当创建多个生命期较短的线程时,程序的执行时间有可能浪费到线程的创建和销毁上,这对于提高程序的性能没有任何帮助。有时,创建大量的线程反而会降低程序的性能,,严重的会使虛拟机崩溃。
线程池包含了若干个准备运行的空闲线程,线程在程序运行的开始创建,可以把创建的Runnable对象交给线程池中的线程运行,运行完成后,如果没有其他任务,线程转人休眠状态,等有任务的时候再唤醒,直到所有任务都执行结束再关闭线程池,从而减少了重复创建和销毁线程的开销。
线程池机制分离了任务的创建和执行。使用线程池执行器,仅仅需要实现Runnable对象,并将该对象交给执行器。执行器会使用线程池中的线程执行,避免额外创建线程的开销。线程池执行器起到了维护和管理线程的作用,从而把程序员从繁重的线程管理任务中解放出来。
从JDK 1.5开始,Java并发库中引人了Executor 框架。该框架包括了接口Executor及其子接口ExecutorService,以及实现了这两个接口的类ThreadPoolExecutor。
为什么要用线程池
1、 降低我们的资源消耗
2、 提高我们的响应速度
- T1:线程创建时间;
- T2:任务运行的时间;
- T3:销毁线程
3、 提高了线程的可管理性
接口Executor
public interface Executor {
void execute(Runnable command);
}
接口Executor的对象可以接收提交到线程池的Runnable任务,该接口实现了任务提交与任务的执行分离。
该接口定义了一个execute的方法,用于异步的执行给定的Runnable对象。
接口ExecutorService
接口ExecutorService从父类接口Executor继承,定义的形式如下:
public interface ExecutorService extends Executor {}
接口ExecutorService提供了关闭线程池的方法shutdown,关闭后的线程池将不再接收新的任务。
类ThreadPoolExecutor
类ThreadPoolExecutor可以用来构建一个线程池,定义的形式如下:
public class ThreadPoolExecutor extends AbstractExecutorService
它有四个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面解释一下构造器中各个参数的含义:
corePoolSize:核心线程数。核心池的大小。在创建了线程池后,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池允许的最大线程数,它表示在线程池中最多能创建多少个线程。
keepAliveTime:空闲的线程存活的时间,表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
unit:参数keepAliveTime的时间单位。
workQueue:一个阻塞队列,用来存储等待执行的任务。一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。
threadFactory:线程工厂,主要用来创建线程。缺省的线程的命名规则: pool-数字+thead-数字。
RejectedExecutionHandler handler :饱和策略。表示当拒绝处理任务时的策略。
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认使用策略)。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
线程池的工作机制
1、如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
线程池的使用范例
public class UseThreadPool {
static class Worker implements Runnable
{
private String taskName;
private Random r = new Random();
public Worker(String taskName){
this.taskName = taskName;
}
public String getName() {
return taskName;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName()
+" process the task : " + taskName);
SleepTools.ms(r.nextInt(100)*5);
}
}
public static void main(String[] args)
{
ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());
//ExecutorService cachePool = Executors.newCachedThreadPool();
//ExecutorService fixPool = Executors.newFixedThreadPool(2);
//ExecutorService singlePool = Executors.newSingleThreadExecutor();
//ExecutorService forkjoinPool = Executors.newWorkStealingPool();
for (int i = 0; i <= 6; i++)
{
Worker worker = new Worker("worker " + i);
System.out.println("A new task has been added : " + worker.getName());
threadPool.execute(worker);
}
threadPool.shutdown();
}
}
A new task has been added : worker 0
A new task has been added : worker 1
A new task has been added : worker 2
A new task has been added : worker 3
pool-1-thread-1 process the task : worker 0
pool-1-thread-2 process the task : worker 1
A new task has been added : worker 4
A new task has been added : worker 5
A new task has been added : worker 6
pool-1-thread-3 process the task : worker 5
pool-1-thread-4 process the task : worker 6
pool-1-thread-2 process the task : worker 2
pool-1-thread-3 process the task : worker 3
pool-1-thread-1 process the task : worker 4
工厂类Executors
在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池。如下所示:
newFixedThreadPool
创建一个固定大小的线程池,空闲线程会一直保留。适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor
创建一个线程的线程池,显然该线程池将顺序执行每一次提交的任务。适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
创建一个会根据需要创建新线程的大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newWorkStealingPool
利用所有运行的处理器数目来创建一个工作窃取的线程池,使用fork/join实现。
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。