基本概念
corepollSize:核心线程数,指线程池运行期间维持的的线程数,也就是线程池的基本大小。线程池启动后来了任务后就会创建线程,如果线程还没执行完任务又来了新任务,就会创建新的线程运行。直至创建的线程数到达核心线程数。
maxpollsize:核心线程数都满了以后还有新的任务到来,就会放入到workqueue中,当workkqueue也满了之后就会创建新的线程直到到达maaxpollsize的大小。maxpollsize就是线程池运行的最大线程数。
keepAliveTime:一个多的线程可以在闲置时存活的最大时间。超过这个时间就会被回收。
workqueue:当核心线程满了且都在运行后又来了新的任务,就会放在任务队列中,当线程处理完后再从队列中取出。
threadfactory:线程工厂,创建线程的工厂,可以设定线程名、线程编号等。
handler:拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。
最大线程数用于处理那些任务量变多的情况,因为一个业务中任务的数量不可能一直一样。
创建线程的过程
增减线程池的特点:
1.通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
2.线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
3.通过设置maximumPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务。
4.只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。
为什么使用线程池
1.降低资源消耗。创建和销毁一个线程都需要消耗资源,所以可以重复使用已创建的线程来减少相关的资源消耗
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。否则就会来一个任务创建一个线程,这个过程需要等待。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的不同
FixedThreadPool:核心线程数和最大线程数相同
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
从源码中看到FixedThreadPoll中创建线程核心线程和最大线程数相同,最大存活时间为0,因为都是核心线程所以最大存活时间没必要设置。后面的参数分别是时间单位和队列。
newSingleExecutor:单线程线程池,只有一个线程在工作。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCacheTreadPool:有缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程。可以回收多余的线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
没有核心线程,最大线程设置为上限,存活60s,使用锁队列。
newScheduledThreadPool:支持定时以及周期性任务执行的线程池。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
类定义
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService
由上看到不同的线程池有不同的特性,但不一定很契合我们的要求,所以我们要自己实现线程池。
线程池的关闭
shutdown:停止线程,不接受新任务,但已有的任务会全部执行。
shutdownNow:立刻停止正在执行的线程,对正在执行的线程会调用interrupt方法,对队列中的任务会返回一个列表,后续的处理由程序员决定。
shutdown源码:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
加锁然后尝试执行四个方法最后解锁。 shutdownnow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
加锁,定义一个队列用于接收还在任务队列里的任务。
线程池的拒绝
什么时候拒绝?
时机:
- 所有线程都在工作并且等待队列已满
- 线程池停止运行
拒绝策略
- AbortPolicy,默认方法,满了以后会抛出异常
- CallerRunsPolicy,调用者运行策略,线程池无法运行,就由任务的提出者运行任务。
- DiscardPolicy,丢弃策略,会丢弃新来的任务。不会有通知
- DiscardOldestPolicy,丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列。
钩子函数
增强线程池的方法
实现原理
线程池的组成:
- 线程池管理器:用于管理线程
- 工作线程:用于执行任务的线程
- 任务队列:暂时未被执行的任务存储,支持并发
- 任务接口:被执行的任务
线程创建的源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取当前状态
int c = ctl.get();
//小于核心线程数,新建线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//判断线程池是否处于运行状态,是,就将任务放入队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//由于线程停止或队列已满,就执行增加线程的方法,如果失败就执行拒绝逻辑
else if (!addWorker(command, false))
reject(command);
}
暂写至此。