线程池概述
通俗的讲,线程池就是一个池子,里面全是线程。目的是对线程进行统一管理,对线程进行复用,对线程数量进行控制,避免过多的线程导致系统缓慢。和线程池紧密关联的是阻塞队列,当线程池中线程全部属于活跃状态时,新进来的请求就需要放在阻塞队列中进行排队等待空闲的线程。
Java中线程池的顶级接口Executor:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
传入一个Runnable对象,并进行执行。
Executors
Java中Executors类提供了许多便捷的方式来创建线程池。
FixedThreadPool:固定线程池。每当提交一个任务就创建一个线程并放入线程池,直到线程池中线程到达最大值,线程池不再变化,线程除非发生了错误不会被回收。当线程池中线程全部处于活跃状态时,有新的获取线程请求,会将其放入LinkedBlockingQueue阻塞队列中,其中阻塞队列无限大。初始化方法如下。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
CachedThreadPool:缓存线程池。如果请求线程池中有空闲线程就分配空闲线程,否则创建一个新的线程并放入线程池,线程池无上限(最大值为Integer最大值2147483647),当一个线程空闲时间超过60S就会被回收。初始化方法如下。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
SingleThreadExecutor:单个线程的线程池。池中只有一个线程,可以用来执行一些有顺序要求的任务(串行执行),当池中县城发生错误时,会创建一个新的线程替代。初始化方法如下。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
ScheduledThreadPool:定时任务线程池。创建一个固定长度线程池,并且可以以定时任务的方式运行任务。初始化方法如下。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
SingleThreadScheduledExecutor:SingleThreadExecutor+ScheduledThreadPool。
ThreadPoolExecutor
我们可以发现,虽然Executors的FixedThreadPool和CachedThreadPool方法返回的都是一个ExecutorService,但是实际上都是一个ThreadPoolExecutor,实际上我们在使用线程池的时候,更加的推荐使用ThreadPoolExecutor,而不是使用Executors,使用ThreadPoolExecutor更加的灵活,方便定制化自己所需要的线程池,也能让自己对线程池的使用更加的透明。
1.ThreadPoolExecutor的最完全构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
corePoolSize:线程池中核心线程大小,默认情况下,如果线程池中的线程数量小于等于corePoolSize,则就算线程超过了keepAliveTime也不会被回收。当一个线程池很少被使用到时,可以使用ThreadPoolExecutor.allowCoreThreadTimeOut(true),来开启超时回收核心线程。
maximumPoolSize:线程池最大大小。
keepAliveTime:线程池中线程最大空闲时间(线程空闲时间达到这个值,会被回收),结合TimeUnit使用。
unit:keepAliveTime的时间单位。
workQueue:线程池的阻塞队列(线程池中线程全部处于活跃状态时,新的任务会进入阻塞队列),通常的实现有LinkedBlockingQueue和ArrayBlockingQueue等。
threadFactory:线程工厂,创建线程的工厂,比如我们想要线程池中的线程命名定制化,就需要自己实现一个ThreadFactory。给上一个简单的NamedThreadFactory实现。
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class NamedThreadFactory implements ThreadFactory {
private static final AtomicInteger TOTAL_THREAD_NUMBER = new AtomicInteger(1);
private final AtomicInteger thisThreadNumber;
private final String threadPrefix;
public NamedThreadFactory() {
this("pool-" + TOTAL_THREAD_NUMBER.getAndIncrement());
}
public NamedThreadFactory(String prefix) {
this.thisThreadNumber = new AtomicInteger(1);
this.threadPrefix = prefix + "-thread-";
}
@Override
public Thread newThread(Runnable runnable) {
String name = this.threadPrefix + this.thisThreadNumber.getAndIncrement();
return new Thread(runnable, name);
}
}
handler:饱和策略,决定了,当阻塞队列被任务堆满时,新进来的任务会被如何处置。ThreadPoolExecutor提供了四种RejectedExecutionHandler的实现。
- ThreadPoolExecutor.CallerRunsPolicy:即caller runs,当阻塞队列满了的时候,其他线程调用execute()方法不再使用线程池,而是直接在主线程(调用线程)执行。
- ThreadPoolExecutor.AbortPolicy:即abort,当阻塞队列满了的时候,直接抛出RejectedExecutionException异常,拒绝该任务。
- ThreadPoolExecutor.DiscardPolicy:即discard,当阻塞队列满了的时候,舍弃新的任务(直接忽略新的execute()),与AbortPolicy不同的是不会抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:即DiscardOldest,当阻塞队列满了的时候,舍弃阻塞队列中最早的任务,再将此任务放入队列。
如果上述几种策略不满足系统的需求,可以自己实现RejectedExecutionHandler接口创建自定义的策略。
2.ThreadPoolExecutor的构造后修改
ThreadPoolExecutor中除了BlockingQueue,其他的都可以通过setXXX方法,在ThreadPoolExecutor构造之后进行修改,如ThreadPoolExecutor.setCorePoolSize(int size);可以修改线程池核心线程大小。
3.关于SingleThreadExecutor
我们可以看到其实SingleThreadExecutor的创建中的FinalizableDelegatedExecutorService类中传入了new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
我们查看源码可以发现,FinalizableDelegatedExecutorService中是包装设计模式包装了ExecutorService,也就是说方法的最后执行者还是ThreadPoolExecutor,这一层包装的目的是不让这个线程池后期进行修改,因为我们上一节讲到ThreadPoolExecutor的构造中的参数都可以后期通过setXXX进行修改。
总的来说new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());和Executors.newSingleThreadExecutor的唯一区别就是,前者可以通过setXXX方式修改变成非单一线程的线程池,而后者无法进行修改。
关于ScheduledThreadPoolExecutor,暂时不进行介绍。上述内容参考《Java并发编程实战》,有兴趣的朋友可以去阅读一下这本书籍。