【java学习】ThreadPoolExecutor 线程池

1,概念

一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行任务的任务队列(阻塞队列)。
默认情况下,在创建了线程池后,线程池中的线程数为0。

1.1 Executor接口

Executor 是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable 类型,从字面意思可以理解,就是用来执行传进去的任务的;
真正的线程池的实现为ThreadPoolExecutor。

1.2 Executors 类

通过工厂类创建了多种ThreadPoolExecutor线程池:
但是一般不这样使用,因为:

  1. FixedThreadPool和SingleThreadExecutor允许请求队列长度为Integer.MAX_VALUE
    可能会堆积大量请求导致OOM
  2. CachedThreadPool和ScheduledThreadPool允许创建线程数量为Integer.MAX_VALUE
    可能会创建大量的线程,从而导致OOM。

1>FixedThreadPool(固定型)

  1. 线程数量固定
  2. 不回收空闲线程
    相当于全部是核心线程,线程池关闭才释放。
  3. 允许请求队列长度为Integer.MAX_VALUE

场景:适合立刻响应执行的任务。

ExecutorService pool = Executors.newFixedThreadPool(2); //核心线程数为2
  //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口 
  Thread t1 = new MyThread(); 
   t1.setPriority(1);//设置线程优先级
  //将线程放入池中进行执行 
  pool.execute(t1); 

2> CachedThreadPool(缓存型)

  1. 线程数量不定;==》全是最大 核心为0
  2. 超过60s空闲线程会回收;
  3. 允许创建线程数量为Integer.MAX_VALUE;

场景:适合执行大量的耗时较少的任务(即生存期较短的异步型任务)。

3> ScheduledThreadPool(调度型)

  1. 核心线程数量固定;
  2. 最大线程数:Integer.MAX_VALUE
  3. 线程可以按schedule依次delay执行,或周期执行。

场景:用于执行定时任务和具有固定周期的重复任务。

4> SingleThreadExecutor(单例型)

  1. 只有一个核心线程
    确保所有的任务都在同一个线程中按顺序执行。
  2. 请求队列长度为Integer.MAX_VALUE
    注意:如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务。

优点:保证所有任务顺序执行。

1.3 ExecutorService 接口

继承了Executor 接口,并声明了一些方法:submit、invokeAll、invokeAny 以及shutDown 等;

1.4 AbstractExecutorService 抽象类

实现了ExecutorService 接口,基本实现了ExecutorService 中声明的所有方法;

1.5 ThreadPoolExecutor 类

继承了类AbstractExecutorService。

2,优点(线程复用和资源控制)

  1. 重用线程池中的线程
    避免因为线程的创建和销毁所带来的性能开销。通过固定线程调用任务的run方法。
  2. 有效控制线程池的最大并发数
    避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
    如果不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存。
  3. 提高线程的可管理性。
    使用线程池可以对线程进行统一的分配和监控,提供定时执行、指定间隔循环执行等功能。
  4. 提高响应速度。
    当任务到达时,任务可以不需要等到线程创建就可以立即执行。

3,参数配置

ThreadPoolExecutor 的构造方法提供了一系列参数来配置线程池。

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory)					

0)新任务进入线程池执行策略

  1. workerCount < corePoolSize
    创建线程,执行新任务。
  2. workerCount >= corePoolSize && 阻塞队列未满
    将任务添加到阻塞队列中。
  3. workerCount >= corePoolSize && workerCount < maximumPoolSize && 线程池内的阻塞队列已满
    创建并启动一个线程来执行新提交的任务。
  4. 如果workerCount >= maximumPoolSize && 线程池内的阻塞队列已满
    根据拒绝策略来处理该任务, 默认是直接抛异常。

1)corePoolSize(核心线程数)

1> 工作机制

默认核心线程一直存活。

2> 参数调优

根据任务性质:

  1. CPU密集型(计算密集型):
    系统的I/O读写效率高于CPU效率。
    大部分的情况是CPU有许多运算需要处理,使用率很高。但I/O执行很快。
    方案:CPU核心数+1。==》过多线程增加上下文切换的次数,带来额外开销。
  2. I/O密集型:
    系统的CPU性能比磁盘读写效能要高很多,大多数情况是CPU在等I/O的读写操作,此时CPU的使用率并不高;
    方案:2*CPU核心数。==》让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
  3. 混合型任务:
    既包含CPU密集型又包含I/O密集型。
    方案:线程池的核心线程数=*(线程等待时间/线程CPU时间+1)CPU核心数;或者分成IO密集型和CPU密集型2个线程池去执行。

2)maximumPoolSize(最大线程数:用于业务繁忙期

1> 工作机制

当活动线程数达到此值,后续新任务将会被阻塞。

2> 参数调优

所以任务优先放入阻塞队列、而不是直接创建最大线程。
==》创建新线程要获得全局锁,影响整体效率。使用队列可以避免频繁的创建和销毁线程,同时阻塞队列会释放cpu资源。
最大线程存在的意义:业务繁忙期使用。
方案:
IO密集型: 2N+1 (N为CPU数量,下同)
CPU密集型:最大线程设置为 N+1

3)keepAliveTime 非核心线程闲置时的超时时长。

超过此时长,非核心线程就会被回收。
当ThreadPoolExecutor的allowCoreThreadTimeOut = true时,可作用于核心线程。

4)unit 用于指定keepAliveTime参数的时间单位

枚举:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

5)workQueue 等待队列

1>参数调优

  1. 尽量配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。
  2. 阻塞队列的选择:
    使用非阻塞队列使用CAS 操作替代锁可以获得好的吞吐量。synchronousQueue 吞吐率最高。
    如果是阻塞队列:
    1)任务超过队列上限:超过的任务阻塞在此等待放入。
    2)队列为空,尝试获取任务的线程会被阻塞(take方法挂起)。==》线程进入wait状态,释放cpu资源。
    3)阻塞队列自带阻塞和唤醒的功能,不需要额外处理。

6)threadFactory 线程工厂

为线程池提供创建新线程的功能。
ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。

7)RejectedExecutionHandler handler 拒绝策略

1>工作机制

默认直接抛异常。
当线程无法执行新任务时,ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者。

2>拒绝策略

拒绝策略说明场景
AbortPolicy(中止策略)默认,抛出异常并中止
CallerRunsPolicy(调用者运行策略)调用者执行任务。
提供了简单的反馈机制,降低新任务提交的速度。可能会阻塞后续任务的执行,影响效率。
不允许失败场景(对性能要求不高、并发量较小)
DiscardPolicy(丢弃策略)直接丢弃任务,无异常。可能丢失数据无关紧要的任务
DiscardOldestPolicy(弃老策略)丢弃最老任务,尝试执行新任务。可能丢失数据发布消息

4,原理

1)线程复用原理

每个线程执行一个循环任务,不停检测是否有任务需要执行。如果有则直接调线程的run方法(不是start方法)。

5,常用方法

ThreadPoolExecutor 类常用方法:

方法说明返回值备注
execute()启动线程池void
submit()启动线程池Future通过捕获Future.get捕获内部抛出的异常。
shutdown()停止接收新任务,原来的任务继续执行,最后关闭线程池
shutdownNow()停止接收新任务,原来的任务停止执行,并关闭线程池取消所有位于阻塞队列中的任务,并将其放入List<Runnable>容器,作为返回值。
awaitTermination()当前线程阻塞并关闭线程池若关闭返回true,否则返回false一般情况下会和shutdown方法组合使用。

1)优雅关闭线程池

  service.shutdown();

        while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
            System.out.println("线程池没有关闭");
        }

但这样也不一定会关闭掉线程池,原因是有些代码里有try-catch,将线程中断作为异常捕获了,导致无法关闭线程池。修改try-catch如下图:
在这里插入图片描述红框里是中断异常,将它捕获并抛出。
第二个catch是正常的try-catch,防止程序崩溃。

6,Demo

通过for循环创建线程,线程池处理线程:
涉及:ThreadPoolExecutor 、CountDownLatch、synchronized的使用。

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class UUIDTest {

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Test
    public void test(){
        //线程安全的list
        List<String> rets = new CopyOnWriteArrayList<>();
        Integer countDown = 10;
        CountDownLatch countDownLatch = new CountDownLatch(countDown.intValue());
        try {
            Object lock = new Object();
            for (; countDown > 0; countDown--) {
                Integer finalCountDown = countDown;
                threadPoolExecutor.execute(() -> {
                    String msg = "";
                    try {
                        synchronized (lock) {
                            log.info("finalCountDown is {}", finalCountDown);
                            //todo: 数据准备
                            msg = "msg content is "  + finalCountDown;
                        }
                        //todo:耗时操作
                        log.info("msg is {}", msg);
                        rets.add(msg);
                    } catch (Exception e) {

                    } finally {
                        countDownLatch.countDown();
                    }
                });
            }

            countDownLatch.await();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        log.info("rets is {}",  rets);
    }
}


@Configuration
public class ThreadExecutorConfig {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                4, 10, 1000, TimeUnit.MICROSECONDS,
                new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        return threadPoolExecutor;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值