线程池
2.1 线程池思想
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。
2.2 线程池概念
概念
:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处
降低资源消耗
。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。提高响应速度
。当任务到达时,任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性
。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2.3 线程池的使用
2.3.1 概述
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
3.3.2 代码实现
3.3.2.1 基于Executors类创建线程静态方法实现
Executors类中有个创建线程池的方法如下:
方法名 | 描述 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重) |
public static newCachedThreadPool() | 用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换) |
public static newSingleThreadExecutor | 创建一个单线程的线程池,适用于需要保证顺序执行各个任务。 |
public static newScheduledThreadPool | 适用于执行延时或者周期性任务。 |
//使用线程池进行多线程代码执行
public class ExecutorsTest {public static void main(String[] args) {//1获取线程池对象//通过Executors工具类对应的静态方法 创建对应的线程池对象ExecutorService executorService = Executors.newFixedThreadPool(2);//2创建执行的任务对象Runnable r=new Runnable() {@Overridepublic void run() {//获取当前线程Thread thread = Thread.currentThread();System.out.println(thread.getName()+"执行了任务");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(thread.getName()+"任务执行完毕,回归线程池");}};//3向线程池提交任务executorService.submit(r);executorService.submit(r);executorService.submit(r);//4关闭线程池executorService.shutdown();}
}
3.3.2.2基于ThreadPoolExecutor自定义线程池填入参数实现
ThreadPoolExecutor核心参数
Executor是线程池的顶级接口,接口中只定义了一个方法 void execute(Runnable command);线程池的操作方法都是定义子在ExecutorService子接口中的,所以说ExecutorService是线程池真正的接口。
ThreadPoolExecutor重要参数:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}- corePoolSize: 线程池核心线程数
- maximumPoolSize:线程池最大数
- keepAliveTime: 空闲线程存活时间
- unit: 时间单位
- workQueue: 线程池所使用的缓冲队列
- threadFactory:线程池创建线程使用的工厂
- handler: 线程池对拒绝任务的处理策略
线程池四种拒绝策略
线程四种拒绝策略,当工作任务大于最大线程 + 阻塞队列会执行阻塞队列。
拒绝策略类型 | 说明 | |
---|---|---|
1 | ThreadPoolExecutor.AbortPolicy | 默认拒绝策略,拒绝任务并抛出任务 |
2 | ThreadPoolExecutor.CallerRunsPolicy | 使用调用线程直接运行任务 |
3 | ThreadPoolExecutor.DiscardPolicy | 直接拒绝任务,不抛出错误 |
4 | ThreadPoolExecutor.DiscardOldestPolicy | 触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入 |
public class MyThreadPoolTest {public static void main(String[] args) {//就是通过创建ThreadPoolExecutor对象 调用构造方法传入参数实现自定义线程池//如果想对线程池添加额外功能可以通过继承ThreadPoolExecutor类 实现//但是必须通过构造方法传入参数 才能实现线程池功能int initSize=3;//初始化线程池中线程个数int maxSize=5;//任务提交后没有空闲线程 扩容后线程池中线程最大个数int keepTime=0;//线程池中线程最大空闲时间(超过后会回收,直至线程数到达初始化大小)0代表不回收TimeUnit timeUnit=TimeUnit.MILLISECONDS;//空闲时间单位 毫秒,枚举类型TimeUnit.SECONDS代表分钟BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();ThreadFactory threadFactory=Executors.defaultThreadFactory();//默认线程创建工厂RejectedExecutionHandler handler= new ThreadPoolExecutor.AbortPolicy();//默认拒绝策略ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(initSize, maxSize, keepTime, timeUnit, queue, threadFactory, handler);Runnable r=new Runnable() {@Overridepublic void run() {//获取当前线程Thread thread = Thread.currentThread();System.out.println(thread.getName()+"执行了任务");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(thread.getName()+"任务执行完毕,回归线程池");}};threadPoolExecutor.submit(r);threadPoolExecutor.submit(r);threadPoolExecutor.submit(r);threadPoolExecutor.shutdown();}
}