线程池
背景:经常创建和销毁线程,消耗特别大的资源,比如并发的情况下的线程,对性能影响很大。线程池就是问题为了解决这个问题,提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回线程池中,可以避免频繁的创建、销毁,实现重复利用。
线程池就是首先创建一些线程,他们的集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务。
优点
- 提高相应速度(减少创建线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理:corePoolSize:核心池的大小。maximumPoolSize:最大线程数。
- keepAliveTime:线程没有任务时最多保持多长时间后终止。
怎么用
1、使用现成的JUC包下的Executors
JUC包下的Executors,打开你的idea随便new个类,轻轻敲出Executors,再加个“.”就能看到它的方法
但是阿里巴巴开发规范不推荐用现成的,因为这几种都可能造成内存溢出,所以最好自己创建,这样也能更好的理解线程池原理;
public class ThreadPool {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("newSingleThreadExecutor当前线程:"+Thread.currentThread().getName());
}
});
}
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(()->{
System.out.println("newFixedThreadPool线程名称:"+Thread.currentThread().getName());
});
}
}
}
2、通过线程工厂自己创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, //核心线程数,可以同时运行的线程数量
10, //最大线程数,任务队列装满的时候,当前可以同时运行的线程数变为这个数
1L,//等待时间,当线程池中的线程数量大于核心线程数,如果没有新的任务过来,核心线程数以外的线程不会立刻被回收,而是等待这个时间到了才进行回收,作用的话我想就是避免线程创建销毁带来的内存消耗吧
TimeUnit.SECONDS,//等待时间的单位
new ArrayBlockingQueue<>(100),//任务队列,100是设置队列容量
new ThreadPoolExecutor.CallerRunsPolicy());//饱和策略,任务队列满了并且当前线程数达到最大线程数会触发这个机制,有四种,先别问,下面会讲
线程池参数
- corePoolSize(必填):核心线程数。
- maximumPoolSize(必填):最大线程数。
- keepAliveTime(必填):线程空闲时长。如果超过该时长,非核心线程就会被回收。
- unit(必填):指定keepAliveTime的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必填):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该队列中。
- threadFactory(可选):线程工厂。一般就用默认的。
- handler(可选):拒绝策略(饱和策略)。当线程数达到最大线程数时就要执行饱和策略。
拒绝策略可选值
- AbortPolicy(默认):放弃任务并抛出RejectedExecutionException异常。
- CallerRunsPolicy:由调用线程处理该任务。
- DiscardPolicy:放弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
- DiscardOldestPolicy:放弃队列最早的未处理任务,然后重新尝试执行任务。
四种饱和策略
任务队列
- 如果当前运行的线程小于corePoolSize,则新任务会直接运行,不会存入队列
- 如果当前运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
- 如果无法将任务加入队列,则创建新的线程,除非创建此线程后线程数会超出 maximumPoolSize,在这种情况下,会触发饱和策略。
线程池执行流程
线程池的工作机制
在线程池的编程模式下,任务是分配给整个线程池的,而不是直接提交给某个线程,线程池拿到任务后,就会在内部寻找是否有空闲的线程,如果有,则将任务交个某个空闲线程。
使用线程池的原因
多线程运行时,系统不断创建和销毁新的线程,成本非常高,会过度的消耗系统资源,从而可能导致系统资源崩溃,使用线程池就是最好的选择。