线程池、线程池核心参数讲解&五种常见线程池的创建与使用&任务提交时线程池处理流程&拒绝处理任务饱和策略

一、什么是线程池?

线程池就是事先创建若干个可执行的线程放入一个池(容器)中,一开始需要的时候直接从池中获取线程,不用自行创建线程,使用完毕后也不需要销毁线程,而是将其放回线程池中,这样可以大大减少创建和销毁线程时所用的时间和资源消耗。这个所谓的容器就是线程池,把线程统一管理的容器。

二、为什么要使用线程池

1.手动创建线程的缺点

继承runnable和callable类是最简单的创建线程的方式,但是这种方式有以下缺点:
(1)不受风险控制
服务器CPU资源有限,如果每个人都显示手动创建线程,不知道哪里的代码出现了多线程,在运行的时候所有线程都在抢占资源,不好控制。
(2)频繁创建开销大
下面是创建一个线程的过程:
①它为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧
②每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成
③一些支持本机方法的 jvm 也会分配一个本机堆栈
④每个线程获得一个程序计数器,告诉它当前处理器执行的指令是什么
⑤系统创建一个与Java线程对应的本机线程
⑥将与线程相关的描述符添加到JVM内部数据结构中
⑦线程共享堆和方法区域

(3)不好管理
线程的名字不统一,可能无限制新建线程,可能占用过多系统资源导致死机或OOM。而重用存在的线程,减少对象创建、消亡的开销,性能佳。

2.线程池的优点

(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。

三、常用的线程池有哪些

1.newSingleThreadExecutor

创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test01 {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            Thread.sleep(200);
            pool.execute(() -> {
                for (int s = 0; s < 10; s++) {
                    System.out.println(Thread.currentThread().getName() + ":" + s);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

在这里插入图片描述

2.newFixedThreadPool

创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小

public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        for(int i =0;i<5;i++) {
            Thread.sleep(200);
            pool.execute(()->{
                for(int s =0;s<5;s++) {
                    System.out.println(Thread.currentThread().getName()+":"+s);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

在这里插入图片描述

3.newCachedThreadPool

创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test03 {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newCachedThreadPool();

        for(int i =0;i<5;i++) {
            Thread.sleep(200);
            pool.execute(()->{
                for(int s =0;s<5;s++) {
                    System.out.println(Thread.currentThread().getName()+":"+s);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

在这里插入图片描述

4.newScheduledThreadPool

创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。


import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class test04 {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        int t =0;
        pool.scheduleAtFixedRate(
                ()-> System.out.println(Thread.currentThread().getName()+":"+t), 0, 2, TimeUnit.SECONDS
        );
    }
}

在这里插入图片描述

5.newSingleThreadScheduledExecutor

创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class test05 {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            int i=1;
            public void run() {
                System.out.println(i);
                i++;
            };
        };
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        // 1表示时间单位的数值 TimeUnit.SECONDS  延时单位为秒
        service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

四、线程池核心参数讲解

1、corePoolSize

线程池的基本大小,通常称作核心线程数,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。
ps:
a.核心线程会一直存活,及时没有任务需要执行
b.当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
c.设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

2、maximumPoolSize

线程池中总共允许的最大线程数,核心线程数+阻塞队列任务数+一部分额外线程数,并不是核心线程数+队列任务数。线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。

3、poolSize

线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize。

4、keepAliveTime

线程空闲时间/空闲线程存活时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize;如果allowCoreThreadTimeout=true,则会直到线程数量=0。
如果一个线程处在空闲状态的时间超过了该属性值,就会因为超时而退出。举个例子,如果线程池的核心大小corePoolSize=5,而当前大小poolSize =8,那么超出核心大小的线程,会按照keepAliveTime的值判断是否会超时退出。如果线程池的核心大小corePoolSize=5,而当前大小poolSize =5,那么线程池中所有线程都是核心线程,这个时候线程是否会退出,取决于allowCoreThreadTimeOut。

5.allowCoreThreadTimeOut

该属性用来控制是否允许核心线程超时退出。默认值为false。如果线程池的大小已经达到了corePoolSize,不管有没有任务需要执行,线程池都会保证这些核心线程处于存活状态。可以知道:该属性只是用来控制核心线程的。
ps:通过corePoolSize和maximumPoolSize,控制如何新增线程;通过allowCoreThreadTimeOut和keepAliveTime,控制如何销毁线程。

6.workQueue

任务队列/阻塞队列/缓冲队列。

五、提交任务时线程池处理流程

1、如果当前线程池的线程数还没有达到核心线程数(基本大小)(poolSize < corePoolSize),无论是否有空闲的线程,直接新增一个线程处理新提交的任务;

2、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务(阻塞)队列未满时,就将新提交的任务提交到阻塞队列排队,等候处理workQueue.offer(command);

3、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列满时;

a.当前poolSize<maximumPoolSize,那么就新增线程来处理任务;

b.当前poolSize=maximumPoolSize,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增的任务,取决于线程池的饱和策略RejectedExecutionHandler。

六、饱和策略

rejectedExecutionHandler:任务拒绝处理器
1、——》两种情况会拒绝处理任务:
a.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
b.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
2.——》线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
3、ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
4、实现RejectedExecutionHandler接口,可自定义处理器
——》》当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理。这个策略默认配置是AbortPolicy,表示无法处理新的任务而抛出异常。JAVA提供了4种策略
1、AbortPolicy:直接抛出异常

2、CallerRunsPolicy:只用调用所在的线程运行任务

3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

4、DiscardPolicy:不处理,丢弃掉。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞飞飞马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值