补:上篇文章中线程池中的全局锁问题
在上篇文章中,提到线程池在创建线程的时候需要获取全局锁,这里我们就看看源码,查看addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core){
// ...
// 省掉上面部分代码
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 提交的任务封装成Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 这里使用可重入锁保证增加线程这一过程的原子性
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//在获得锁期间再次检查线程池的运行状态:如果
//线程池已经关闭或者任务为空则抛出异常
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
//加入Workers
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//如果添加成功则启动线程执行任务
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
从上面的源码中我们看到线程池新建线程时使用了可重入锁ReentrantLock ,会影响线程池的并发效率,这里就引出了线程池中阻塞队列的作用:缓冲,将提交上来的任务先放进阻塞队列中,等到线程池中有可用的工作线程时,再去执行对应的任务,一方面缓冲由全局锁导致的并发效率问题,一方面让线程池中的线程达到复用的目的,减少系统资源开销。下面我们就来详细介绍下线程池
使用线程池的目的
众所周知,线程是稀缺资源,线程的创建和销毁都对系统资源造成很大的消耗,我们使用线程池,线程可以到达复用,从而降低资源消耗;当任务提交上来,无须等待线程创建就可以立即使用可用的线程去执行任务, 提高了任务的响应速度;使用线程池可以进行统一的分配、调优和监控线程。
线程池ThreadPoolExecutor详细介绍
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- corePoolSize:核心线程池大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建
- maximumPoolSize:线程池运行创建的最大线程数,如果队列已满,并且已创建的线程数小于maximumPoolSize时,则线程池会再创建新的线程执行任务,(如果使用了无界队列则这个参数就失效了)
- keepAliveTime:线程保活时间,线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
- TimeUnit: 线程保活时间单位,使用了枚举类TimeUnit
- BlockingQueue:阻塞队列,用于保存等待执行的任务,有:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 后面的文章讲单独详细介绍这些工作队列。
- ThreadFactory:用于设置创建线程的工厂,可以使用线程工厂给每个创建的线程设置更有实际生产意义的名字,方便出错时回溯。默认使用的是Executors.defaultThreadFactory(),当然我们也可以实现ThreadFactory接口去自定义,也可以使用开源框架guava提供的ThreadFactoryBuilder快速给线程池的线程设置有意义的名字。
- RejectedExecutionHandler:饱和策略,当工作队列和线程池都已满时,此时线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认为:AbortPolicy,可选有CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
ThreadPoolExecutor简单使用
上面介绍的线程池中各个组件,后面会逐一地结合上对应的例子详细介绍,这里我们先写一个简单的线程池例子。后面文章中的举例也基本在此例子上进行扩展。
public class ThreadPoolExecutorTest {
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new
ArrayBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args){
for (int i = 1; i <= 14; i++) {
Callable<Boolean> task = createTask(i);
// 提交任务到线程池
pool.submit(task);
// 打印下线程池的基本信息
System.out.println("after task:" + i + " submited, current active count: "
+ pool.getActiveCount() + ", size of queue: " + pool.getQueue().size());
}
// 关闭线程池
pool.shutdown();
}
/**
* @Author Xu hao
* @Description 创建任务
* @Date 2019/3/19 0:08
* @param i
* @return java.util.concurrent.Callable<java.lang.Boolean>
**/
private static Callable<Boolean> createTask(int i){
Callable<Boolean> callable = () -> {
TimeUnit.SECONDS.sleep(10);
System.out.println("thread: " + Thread.currentThread().getName() + " execute task: " + i);
return true;
};
return callable;
}
}
执行结果的部分日志如下:
after task:1 submited, current active count: 1, size of queue: 0
after task:2 submited, current active count: 2, size of queue: 0 // 此时两个核心线程,去执行两个任务 刚刚好 工作队列闲置
after task:3 submited, current active count: 2, size of queue: 1 // 此时开始核心线程已满,任务开始放入工作队列中
after task:4 submited, current active count: 2, size of queue: 2
after task:5 submited, current active count: 2, size of queue: 3
after task:6 submited, current active count: 2, size of queue: 4
after task:7 submited, current active count: 2, size of queue: 5
after task:8 submited, current active count: 2, size of queue: 6
after task:9 submited, current active count: 2, size of queue: 7
after task:10 submited, current active count: 2, size of queue: 8
after task:11 submited, current active count: 2, size of queue: 9
after task:12 submited, current active count: 2, size of queue: 10
after task:13 submited, current active count: 3, size of queue: 10 // 此时,核心线程已满,工作队列也已满,创建新的线程执行任务
after task:14 submited, current active count: 4, size of queue: 10