先说说我个人对线程池的理解:线程池顾名思义是一个装有很多线程的池子,这个池子维护着从线程创建到销毁的怎个生命周期以及线程的分配,用户只需要把任务提交给这个线程池而不用去关心线程池如何创建线程,线程池会自己给这些任务分配线程资源来完成任务。
java的Executor线程池框架类图大致如下:
要深入分析这个线程池框架,我们先从使用者的角度入手进行分析,要使用这个框架首先得有个可执行的线程任务(实现了Runnable或Callable接口的类):
public class Task implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
然后通过静态工厂类Executors产生一个线程池,然后将这个任务提交给线程池。
public class TaskTest {
public static void main(String[] args){
ExecutorService exec = Executors.newFixedThreadPool(3); // 核心线程为3的线程池,最大线程为3的线程池,池子中只会有三个线程
// ExecutorService exec = Executors.newCachedThreadPool(); // 核心线程为0,最大线程为Integer.MAX_VALUE的线程池
// ExecutorService exec = Executors.newScheduledThreadPool(3); // 核心线程为3的延迟线程池
// ExecutorService exec = Executors.newSingleThreadExecutor(); // 相当于Executors.newFixedThreadPool(1);
for (int i = 0; i < 5; i++) {
exec.execute(new Task());// 提交五个任务给线程池
}
}
}
从结果中可以看到,虽然提交了五个任务,但是始终是由三个线程执行的。
在以上的使用中我们通过静态工厂类产生了一种类型的线程池,辣么,就从这个Executors入手进行分析java的这个线程池框架,先看看Execuotrs中几种线程池的静态工厂方法:
1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
2.newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
3.newCachedThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
对于每一种的线程池工厂方法都可以在参数处传入一个ThreadFactroy对象,用来构造线程,线程池将按照你的ThreadFactroy中的newThread方法来产生线程,如果不传入这个参数,默认使用Executors的静态内部类DefaultThreadFactroy作为线程生成策略。
DefaultThreadFactroy源码如下:
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
默认的线程工厂会给线程分好组然后给线程设置名字,设置线程为非守护线程,并且将线程优先级设为Thread.NORM_PRIORITY (该值为5,默认线程优先级都是5)
从上面的三种线程池的静态工厂方法中可以看出,通过在new ThreadPoolExecutor的时候传入不同的参数就可以得到不同的线程池了,下面先看看ThreadPoolExecutor构造方法中的各个参数及含义:
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //非核心线程存活时间
TimeUnit unit, //时间单位
BlockingQueue workQueue, //排队线程的阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) { //当前线程已达到最大线程数,且阻塞队列已满时的异常处理策略
}
对于以上各参数的理解,可以举一个栗子进行讲解:
我们可以自定义一个 核心线程数为1,最大线程数为2,非核心闲置线程存活时间为100毫秒,阻塞队列大小为2的线程池如下:
ExecutorService executorService = new ThreadPoolExecutor(
1, //核心线程
2, //最大线程
100, //闲置线程存活时间
TimeUnit.MILLISECONDS, //时间单位:毫秒
new ArrayBlockingQueue(2), //大小为2的阻塞队列
Executors.defaultThreadFactory(), //使用Executors类的默认线程工厂
new RejectedExecutionHandler() { //new 一个匿名内部类实现RejectedExecutionHandler接口,作为异常处理策略
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (r instanceof Task){
System.out.println( "task:" + ((Task) r).taskId + " is Handel!");
try {
TimeUnit.MILLISECONDS.sleep(51);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
创建一个任务,每个任务有一个自己的id:
public class Task implements Runnable {
private static AtomicInteger number = new AtomicInteger(0);
public int taskId = number.addAndGet(1);
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " task:" + taskId + " start...");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " task:" + taskId + " end...");
}
}
接下来向这个线程池提交10个任务:
for (int i = 0; i < 10; i++) {
executorService.execute(new Task());// 提交十个任务给线程池
}
executorService.shutdown();
System.out.println("main is over");
先看看运行结果:
pool-1-thread-1 task:1 start...
pool-1-thread-2 task:4 start...
task:5 is Handel!
task:6 is Handel!
pool-1-thread-1 task:1 end...
pool-1-thread-2 task:4 end...
pool-1-thread-1 task:2 start...
pool-1-thread-2 task:3 start...
task:9 is Handel!
task:10 is Handel!
pool-1-thread-1 task:2 end...
pool-1-thread-2 task:3 end...
pool-1-thread-1 task:7 start...
pool-1-thread-2 task:8 start...
main is over
pool-1-thread-1 task:7 end...
pool-1-thread-2 task:8 end...
结果中: pool-1表示那个线程池,-thread-1表示哪一个线程在执行任务,task:1表示哪一个任务被执行, start... end...表示任务开和结束
分析一下运行过程:
总结一下:当向线程池中添加任务时,首先会从核心线程池中获取线程执行任务,核心线程池中没有空余线程时,任务会进入阻塞队列,阻塞队列满了之后,线程池会分配非核心线程,直到池子里的所有线程达到最大值,此时会将任务交由异常处理策略处理,非核心线程在执行完任务之后如果空闲时间超过设定值将会被回收,下一次需要的时候又重新被创建,核心线程不会被回收。
通过以上例子,相信大家对线程池已经有了一个比较深入的了解,对于静态工厂类Executors提供的几个线程池也能比较好的理解了,在接下来的一篇文章中,我会深入剖析在jdk1.8中线程池核心类ThreadPoolExecutor这个类是如何实现以上的功能的。