Java并发编程八 线程池
1.线程池作用
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2. 线程池ThreadPoolExecutor
我们可以通过ThreadPoolExecutor类来自定义线程池。
首先看ThreadPoolExecutor的构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2.1 corePoolSize(核心线程数)
当提交一个任务到线程池时,如果当前线程池存活的线程数小于corePoolSize则创建一个新的线程执行任务。
如果当前线程数量大于等于corePoolSize 则不会继续创建核心线程而是将任务存入到BlockingQueue中。
如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
2.2 maximumPoolSize(最大线程数)
线程池允许创建的最大线程数。如果阻塞队列BlockingQueue满了,并且当前的线程数量小于最大线程数时,线程池会继续创建非核心线程。直到线程池中的线程数量等于maximumPoolSize就不会再继续创建非核心线程了。
2.3 keepAliveTime(线程空闲时存活时间)
该线程池中非核心线程闲置超时时长
一个非核心线程,闲置状态的时长超过这个参数所设定的时长,就会被销毁掉
如果设置allowCoreThreadTimeOut = true,则会作用于核心线程
2.4 unit (存活时间单位)
keepAliveTime的单位,TimeUnit是一个枚举类型。
2.5 workQueue (阻塞队列)
当提交任务时,核心线程数量达到corePoolSize时,会将任务存入阻塞队列中。
阻塞队列有以下几种
ArrayBlockingQueue(有界队列)
有界队列,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则进行拒绝策略。
LinkedBlockingQueue(无界队列)
无界队列,接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize。
SynchronousQueue(同步队列)
同步队列,一种没有缓冲的队列,生产者生产的数据会直接被消费者获取并消费。
DelayQueue(延时队列)
队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。
2.6 threadFactory (线程工厂)
创建线程的方式,一般很少自己实现。
2.7 handler (拒绝策略)
当阻塞队列满了并且线程池中线程数已经达到最大值时,说明线程池处于饱和状态。如果再有任务进入线程池中则会调用拒绝策略进行拒绝任务。
AbortPolicy,直接抛出异常系统继续正常工作。
CallerRunsPolicy:只要线程未关闭,该策略直接在调用者线程中,运行当前被丢失的任务(别拒绝的策略不会在线程池中的线程内执行,而是在开启线程池的线程中执行,也就是main线程)。
DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务。
DiscardPolicy:丢弃无法处理的任务,不给任何处理。
当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
线程池执行任务流程图
2.Executors类
Jdk并发包给我们提供了四种常用的创建线程池的方法。
newFixedThreadPool
创建一个固定数量的核心线程池方法。
核心线程数与最大线程数量相等
空闲存活时间为0(无意义,因为没有非核心线程)
阻塞队列为无界队列
当任务提交到线程池中,会创建nThreads个线程执行任务,如果超出会将所有任务存入到无界队列内。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
缓存线程池
核心线程为0
最大线程为Integer.MAX_VALUE
存活时间为60秒
当任务提交到线程池中会创建线程,如果线程空闲超过60秒会销毁空闲线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newSingleThreadExecutor
一个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
定长线程池:可以延迟执行任务或者周期性的执 行某个任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
其静态方法返回ScheduledExecutorService 类型的对象,ScheduledExecutorService接口有四个方法进行提交任务
schedule方法:延时执行一次任务
scheduleWithFixedDelay方法:以上一个任务执行完以后的固定时间周期执行
scheduleAtFixedRate方法:以固定时间周期执行,不管上一个线程是否执行完毕。
3.ExecuterService接口
通过创建连接池返回 ExecuterService接口类型的对象 ,而不得不说ExecuterService接口中的几个方法
3.1 void execute(Runnable command);
execute方法,只是执行传入实现Runnable接口的实例对象,并没有返回值。
3.2 Future submit(*);
submit方法有三种形式,参数不同。但是返回值都是FutureTask类的实例对象。
查看源码可知:
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
其实内部都new 一个FutureTask实例对象返回。
再次查看FutureTask源码的构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
// Executors.callable(runnable, result); 源码最终调用结果
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
从源码中可以看出
submit(Runnable task,T result) 只是将task执行并且返回传入的result值。
而submint(Callable task) 会返回我们实现call()方法的return的值。