线程池就是首先创建一些线程,他们的集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后再需要执行新的任务时重用这些线程而不是新建线程
1. 为什么要使用线程池
多线程运行时,系统不断创建和销毁新的线程,成本非常高,会过度的消耗系统资源,从而可能导致系统资源崩溃,使用线程池就是最好的选择
2、创建线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
- corePoolSize:线程池核心线程数量
- maximumPoolSize:线程池最大线程数量
- keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
- unit:存活时间的单位
- workQueue:存放任务的队列
- handler:超出线程范围和队列容量的任务的处理程序
示例:
通过Executor工厂类中的静态方法获取线程池对象
1、通过newCachedThreadPool获取线程池对象
该方式特点是:创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
//1.使用工厂类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2.提交任务
for (int i = 1; i <= 10; i++) {
es.submit(new MyRunnable(i));
}
2、通过newFixedThreadPool获取线程池对象
该方式特点是:可指定创建线程数,并且可以重复用
//1.使用工厂类获取线程池对象
ExecutorService es = Executors.newFixedThreadPool(3);
//2.提交任务
for (int i = 1; i <= 10; i++) {
es.submit(new MyRunnable2(i));
}
3、通过newSingleThreadExecutor获取线程池对象
该方式特点是:只会创建一个线程
//1.使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor();
//2.提交任务
for (int i = 1; i <= 10; i++) {
es.submit(new MyRunnable3(i));
}
小结
三种创建线程池的区别 第一种:newCachedThreadPool:线程的数据是不做限制的,每次有任务来的时候都会以任务优先,性能最大化(也就是服务器压力比较大)
第二种:newFixedThreadPool:可以让压力不那么大,并且可以规定线程的数量,当线程的数量达到指定数量的时候,这个时候就不会再有新的线程了
第三种:newSingleThreadExecutor:绝对的安全,不考虑性能,因为是单线程,永远只有一个线程来执行任务。
3. 线程池的工作流程
- 核心线程数:5
- 存放要执行任务的等待队列:10
- 最大线程数:20
- 拒绝策略:
工作过程:
1、提交任务,判断核心线程是否已满,如果未满,线程池调用addWorker(Runnable firstTask, boolean core)方法启动执行firstTask就是调用execute(Runnable command)方法中所传的那个command,core是该线程是否为核心线程,如果此时firstTask为null并且任务队列不为空则结束创建新的线程,让已有的核心线程处理,否者创建一个Worker对象执行任务,
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) {
runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this);
}
}
最后启动firtask任务
2、判断阻塞队列是否已满,如果未满,将任务放入队列中,等待执行,如果已满,进入下一步
3、如果线程池中的最大线程未满,创建线程执行任务,如果已满,按照拒绝策略进行处理。
注意:新建的非核心线程会先执行新进入的任务,并非等待队列中的任务。
4、当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
默认策略:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务