最近想了解一下Java线程池的相关知识,看到Effective Java上和一篇博客,《深入理解Java之线程池》 — http://www.importnew.com/19011.html,看完受益良多,写一篇博客做一下笔记。
通常来说,我们需要使用线程的时候,就会创建一个Runnable或者xxxThread,然后执行。
而在并发编程中,如果有多个线程工作,并且每个线程工作时间很短,那么频繁的创建和销毁线程的开销是很大的。
因此我们引入了线程池的概念。池里有现成的线程,我们从池里获取可用线程,如果没有则创建新的线程或等待已有线程运行完其任务。运行结束的线程会回到线程池中,等待使用或者终止。
一、Java线程池的分析
在Java中,使用 ThreadPoolExecutor 或者 Executors 来创建线程池,提供四种模式。
(1)Executors.newCachedThreadPool:创建一个可缓存线程池,可以回收空闲的线程,如果任务数量太多,会新建线程。适合小程序和轻载服务器,不适合大负载的服务器,如果负载太重,CPU完全被占用了,当有更多任务时,会创建更多线程,情况会更糟。
(2)Executors.newFixedThreadPool:创建一个包含固定线程数目的线程池,超过线程数量的任务会进行等待。可以解决大负载服务器的并发问题。
(3)Exectors.newScheduledThreadPool:创建一个定长线程池,可以定时和周期性地执行任务,可以代替Timer。
(4)Executors.newSingleThreadExecutor:创建一个单线程的线程池,保证所有任务按指定顺序执行。
其实这几个线程池都来自同一个方法构造,来看看构造方法就一目了然:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
//这个多了一层,最终也是调用了ThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//此处的super就是ThreadPoolExecutor的构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
来看看ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
各个参数的含义:
corePoolSize:核心池的大小,当线程数量达到corePoolSize之后,新的任务会进入缓存队列。
maximumPoolSize:最大池大小,表示线程池中最大能创建多少线程。例如在缓存池中,新建任务可以超过corePoolSize,但是不能超过maximumPoolSize。
keepAliveTime:线程在没有任务执行时,最多经过多久会自动终止。一般来说,只有当线程数量超过corePoolSize时,这个参数会起作用。如果设置了allowCoreThreadTimeOut(boolean),方法,则也会起作用,直到池中线程数量为0。
unit:时间keepAlivetime的单位,TimeUnit类,是一个枚举类型。其中有
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:阻塞队列,等待执行的任务就存储在这个队列之中。有多种选择:
ArrayBlockingQueue; //数组实现
LinkedBlockingQueue; //链表实现
PriorityBlockingQueue; //优先队列实现
SynchronousQueue; //这个好玩,自身是一个空队列,没有remove操作,就不能insert,也无法peek。
...
顺便看一下阻塞队列的实现,找其中一个方法看看可以有所了解:
public void putLast(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (!linkLast(node))
notFull.await();
} finally {
lock.unlock();
}
}
其实是使用了一个可重入锁,然后用notFull,相当于操作系统中的一个信号量,来等待加入队列成功。
threadFactory:线程工厂,用来创建新的线程。
handler:表示当拒绝处理任务时的策略。有以下四种类型:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
通过以上几个参数,可以看出如何配置和使用线程池。
二、使用
可以使用Executors的静态方法来创建,也可以通过ThreadPoolExecutor来创建,前者比较简洁,后者比较灵活。
1、使用ThreadPoolExecutor的示例:
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
2、(MyTask类不变)ExecutorService没有getPoolSize方法,有的线程池可以强转。
(1)使用静态方法创建Single线程池,无法强转:
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for(int i = 0; i < 10; i++){
MyTask myTask = new MyTask(i);
threadPool.execute(myTask);
System.out.println("execute task: " + i);
}
threadPool.shutdown();
}
结果按顺序执行:
正在执行task 0
task 0执行完毕
正在执行task 1
task 1执行完毕
正在执行task 2
task 2执行完毕
正在执行task 3
task 3执行完毕
正在执行task 4
task 4执行完毕
正在执行task 5
(2)创建定长线程池:
public static void main(String[] args) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(5);
for(int i = 0; i < 10; i++){
MyTask myTask = new MyTask(i);
threadPool.execute(myTask);
System.out.println("pool size: " + threadPool.getPoolSize()
+ " queue size: " + threadPool.getQueue().size());
}
threadPool.shutdown();
}
结果:
正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
pool size: 5 queue size: 1
pool size: 5 queue size: 2
pool size: 5 queue size: 3
pool size: 5 queue size: 4
pool size: 5 queue size: 5
正在执行task 4
task 0执行完毕
正在执行task 5
task 1执行完毕
task 2执行完毕
正在执行task 6
正在执行task 7
task 3执行完毕
正在执行task 8
task 4执行完毕
正在执行task 9
task 5执行完毕
task 7执行完毕
task 6执行完毕
task 8执行完毕
task 9执行完毕
(3)
创建缓存线程池:
public static void main(String[] args) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool();
threadPool.setCorePoolSize(5);
for(int i = 0; i < 10; i++){
MyTask myTask = new MyTask(i);
threadPool.execute(myTask);
System.out.println("pool size: " + threadPool.getPoolSize()
+ " queue size: " + threadPool.getQueue().size());
}
threadPool.shutdown();
}
结果:
正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
正在执行task 4
pool size: 6 queue size: 0
正在执行task 5
pool size: 7 queue size: 0
正在执行task 6
pool size: 8 queue size: 0
正在执行task 7
pool size: 9 queue size: 0
正在执行task 8
pool size: 10 queue size: 0
正在执行task 9
task 0执行完毕
task 1执行完毕
task 4执行完毕
task 3执行完毕
task 2执行完毕
task 7执行完毕
task 5执行完毕
task 9执行完毕
task 8执行完毕
task 6执行完毕
对比结果,可以看出
单线程的线程池是顺序执行。
定长线程池,超出定长的任务会在阻塞队列中等待。
缓存线程池,超出corePoolSize,仍然会创建新的线程来执行任务。
关于缓存线程池,有趣的是,
设置了缓存线程池最大为6之后,
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool();
threadPool.setCorePoolSize(5);
threadPool.setMaximumPoolSize(6);
//丢弃任务,不抛出异常
threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
for(int i = 0; i < 10; i++){
MyTask myTask = new MyTask(i);
threadPool.execute(myTask);
System.out.println("pool size: " + threadPool.getPoolSize()
+ " queue size: " + threadPool.getQueue().size());
}
threadPool.shutdown();
}
结果是这样的:
正在执行task 0
pool size: 1 queue size: 0
pool size: 2 queue size: 0
正在执行task 1
pool size: 3 queue size: 0
正在执行task 2
pool size: 4 queue size: 0
正在执行task 3
pool size: 5 queue size: 0
正在执行task 4
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
pool size: 6 queue size: 0
正在执行task 5
task 1执行完毕
task 2执行完毕
task 0执行完毕
task 3执行完毕
task 4执行完毕
task 5执行完毕
后面的6-9全部都被丢弃了。思考了一下,可能是主线程执行太快,缓存池中的线程全都没执行完,后面任务就直接丢弃了。
修改for循环:
for(int i = 0; i < 10; i++){
MyTask myTask = new MyTask(i);
Thread.sleep(1000);
threadPool.execute(myTask);
System.out.println("pool size: " + threadPool.getPoolSize()
+ " queue size: " + threadPool.getQueue().size());
}
主线程每次休眠1s,结果0-9全部执行了。
由此也可以看出,如果当前任务数量大于maximunPoolSize,那么则会拒绝执行,执行handler的策略。
第一次结合源码写博客,如有错误请指正~