1.什么是线程池
为了避免系统频繁地创建和销毁线程,消耗系统资源,我们可以让创建的线程进行复用。就如同数据库的连接池一样,当系统需要用到数据库时,并不是创建一个新的连接,而是从连接池中获取一个连接。线程也类似,当需要用到线程时就可以去池子中拿一个空闲线程,用完之后就“还”给线程池,通过这种方式可以节约不少创建和销毁线程的时间。
2.JDK对线程池的支持
Executor结构图:
其中ThreadPoolExecutor表示一个线程池,Executors类则扮演这线程池工厂的角色。通过这个工厂可以获得一下类型的线程池:
public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newSingleThreadExecutor();
public static ExecutorService newCachedThreadPool();
public static ScheduledExecutorService newSingleThreadScheduledExecutor();
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
1.newFixedThreadPool()方法:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有则新的任务会被暂存在一个任务队列中,待有线程空闲时在执行。
2.newSingleThreadExecutor()方法:该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
3.newCachedThreadPool()方法:该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
4.newSingleThreadScheduledExecutor()方法:该方法返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService接口在ExecutorService接口上扩展了在给定时间执行某任务的功能。
5.newScheduledThreadPool()方法:和newSingleThreadScheduledExecutor()方法不同的是该线程池可以指定线程数量。
2.1计划任务
与其他几个线程池不同,ScheduledExecutorService并不会一定会立即安排执行任务。它其实是起到了计划任务的作用。它提供了一下几个方法
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public ScheduledFuture<?> shceduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
方法schedule()会在给定时间,对任务进行一次调度。方法shceduleAtFixedRate和方法scheduleWithFixedDelay会对任务进行周期性调度。对于FixedRate来说,任务调度的频率是一定的,它是以上一个任务开始执行时间为起点,之后的period时间调度下一次任务。而FixDelay则是在上一个任务结束后,再经过Delay时间进行任务调度。
2.2核心线程池的内部实现
线程池均使用了ThreadPoolExecutor实现。以下是三个线程池的实现方法:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从以上代码实现来看,他们只是ThreadPoolExecutor。下面是其最重要的构造函数
public ThreadPoolExecutor(int corePoolSize, //指定线程池中的线程数量
int maximumPoolSize, //指定线程池中的最大线程数量
long keepAliveTime, //当线程数量超过corePoolSize时,多余空闲线程的存活时间
TimeUnit unit, //存活时间的单位
BlockingQueue<Runnable> workQueue, //任务队列,被提交但尚未被执行的任务
ThreadFactory threadFactory, //线程工厂,用于创建线程,一般为默认
RejectedExecutionHandler handler); //拒绝策略,当任务太多来不及处理,如何拒绝任务
参数workQueue任务队列,是一个BlockingQueue接口的对象,用于存放Runnable对象。根据队列功能的分类,可使用以下几种BlockingQueue:
1.直接提交队列:由SynchronousQueue对象提供。它是一种特殊的BlockingQueue。SynchronousQueue没有容量,每一个插入操作对应着一个删除操作,反之也一样。提交的任务不会被真实保存,而是将新任务提交给线程执行。
2.有界任务队列:可以使用ArrayBlockingQueue实现。若有新的任务需要执行时,如果线程池的实际线程数小于CorePoolSize,则会优先创建新的线程,若大于CorePoolSize,则会将新的任务加入等待队列。若队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的进程执行任务。若大于,则执行拒绝策略。
3.无解任务队列:通过LinkedBlockingQueue类实现。与有界队列相比,除非资源耗尽,否则无界的任务队列不存在任务入队失败的情况。
4.优先任务队列:优先任务队列是带有执行优先级的队列。通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序,是一种特殊的无解队列。
2.3.1拒绝策略
ThreadPoolExecutor的最后一个参数是拒绝策略。当系统超负荷运行时就会执行拒绝策略,JDK内置的拒绝策略如下:
1.AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
2.CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是任务提交线程的性能极有可能会急剧下降。
3.DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
4.DiscardPolicy策略:该策略默默地丢弃无法处理的任务,不予任何处理。
以上内置的策略均实现的RejectedExecutionHandler接口,当然也可以自己扩展接口,自定义自己的策略。接口定义如下:
public interface RejectedExecutionHandler{
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
2.3.2自定义线程创建工厂:ThreadFactory
ThreadFactory是一个接口,只有一个方法用于创建线程,我们可以实现这个接口,自定义线程创建工厂:
Thread newThread(Runnable r);
3.扩展线程池
ThreadPoolExecutor是一个可以扩展的线程池。它提供了beforExecute(),afterExecute(),terminated()三个接口线程池进程扩展,线程池扩展对线程池运行状态的跟踪,输出一些有用的调试信息以帮助系统故障诊断,如:
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExtThreadPool {
public static class MyTask implements Runnable{
public String name;
public MyTask(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("正在执行"+":Thread Id:"+Thread.currentThread().getId()+",Task Name="+name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())
{
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("准备执行: "+((MyTask)r).name);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("执行完成: "+((MyTask)r).name);
}
@Override
protected void terminated() {
System.out.println("线程退出");
}
};
for(int i=0; i<5; i++)
{
MyTask task = new MyTask("TASK-GEYM-"+i);
es.execute(task);
Thread.sleep(10);
}
es.shutdown();
}
}
4.优化线程池线程数量
计算公式如下: