浅谈线程池

本文介绍了线程池的概念、为何使用、JDK提供的Executor框架及ThreadPoolExecutor核心参数,还通过实例演示了线程池的创建、工作原理和ThreadFactory的作用。讨论了CPU密集型和IO密集型任务的线程配置建议。
摘要由CSDN通过智能技术生成

1、线程池

  1. 线程的创建和关闭依然需要花费时间,如果每个任务都开一个线程,那么可能就会出现创建+销毁的时间> 任务真实的时间。
  2. 其次,线程也是占用内存的,大量线程处理不当可能出现OOM异常。
  3. 因此大量的线程只会拖垮应用系统。需要对线程进行控制和管理。

1.1、什么是线程池?

  1. 简单的说:当想要使用线程的时候,直接去线程池中,当然,线程池中线程的个数也是有限的。当一个线程完成工作的时候,将这个线程返回线程池中。在这里插入图片描述
  2. 特点:线程复用、控制最大并发数、管理线程

1.1.1、为什幺要使用线程池?

  1. 多核处理的好处是省略上下文的切换开销
  2. 好处
    1. 降低资源消耗。
    2. 提高响应速度。(任务到达,不需要等到线程创建就可以立刻执行)。
    3. 提高线程的可管理性。

1.2、JDK对线程池的支持

  1. JDK提供了Executor框架去控制多线程。本质就是一个线程池。
  2. 架构图如下图所示:
    在这里插入图片描述
  3. Executors类是一个线程池工厂角色,通过它可以拥有一个特定功能的线程池。而ThreadPollExecutor类实现了Executor接口,所以,任何Runnable对象可一个被ThreadPoolExecutor线程池调度。
  4. Executors类提供的几个常见创建线程池的方法:
// 创建一个拥有i个线程的线程池
Executors.newFixedThreadPool(int i)
//创建一个只有1个线程的线程池
Executors.newSingleThreadExecutor()
//创建一个可扩容的线程池
Executors.newCacheThreadPool()
//线程池支持定时以及周期性执行任务,创建一个corePoolSize为传入参数,最大线程数为整形的最大数的线程池
Executors.newScheduledThreadPool(int corePoolSize)

1.3、核心线程池的内部实现

  1. 查看上述方法的源码:
    1. newFixedThreadPool(int i)
      public static ExecutorService newFixedThreadPool(int nThreads) {
      	return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
      }
      
    2. newSingleThreadExecutor()
      public static ExecutorService newSingleThreadExecutor() {
      	return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>()));
      }
      
    3. newCachedThreadPool()
      public static ExecutorService newCachedThreadPool() {
      	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
      }
      
    4. newScheduledThreadPool(int corePoolSize)
      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
      	return new ScheduledThreadPoolExecutor(corePoolSize);
      }
      //继承了ThreadPoolExecutor
      public ScheduledThreadPoolExecutor(int corePoolSize) {
      	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
            new DelayedWorkQueue());
      }
      
  2. 所以,发现最后都指向了ThreadPoolExecutor

1.3.1、线程池的七大核心参数

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. corePoolSize:核心线程数,线程池中的常驻核心线程数。
  2. maximumPoolSize: 线程池能够容纳同时执行的最大线程数,此值必须大于等于1。
  3. keepAliveTime:多余的空闲线程存活时间。
  4. unit:keepAliveTime的单位
  5. workQueue:任务队列,被提交的但未被执行的任务。就是一个阻塞队列。
  6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程池,一般用默认即可
  7. handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(maximumPoolSize3)时,如何来拒绝请求执行的Runnable的策略。

1.3.2、拒绝策略

  1. 所有的拒绝策略都实现了RejectedExecutionHandler接口
    在这里插入图片描述
    1. AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行。
    2. DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果运行任务丢失,这是一种好方案。
    3. CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者(从哪儿来的回哪儿去)。
    4. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务(喜新厌旧)。

1.4、工作原理

请添加图片描述

  • 场景举例:银行取钱
  1. 银行周末只开两个窗口,此时正好来了两个人,那么正好处理完毕。
  2. 或者两个人办理业务时间很长,而后续又有很多人进入银行,新来的客户进入等待区。
  3. 如果此时还有办理业务的人,人多到超过了等待区能容纳的数量,此时就开放所有的窗口,此时新增的窗口接待非等待区的。
  4. 如果人继续增多,导致接待不过来了,执行拒绝策略了,不能出现踩踏事件。
  5. 当人流减少,或者没有人再进入银行,那么就新开的窗口慢慢的在规定时间内就会关闭,最后回到常规开放的两个窗口。
  • 工作原理
  1. 在创建线程池之后,等待提交过来的任务请求。
  2. 当调用execute()方法添加一个请求任务的时候,线程池做判断:
    2.1. 如果正在运行的线程数量小于corePoolSize,那么就马上创建线程运行这个任务。
    2.2. 如果正在运行的线程数量大于等于corePoolSize,那么将这个请求任务放入阻塞队列中。
    2.3. 如果队满并且运行的线程数量小于maximumPoolSize,那么创建非核心线程立刻运行这个任务。
    2.4. 如果队满并且运行线程数量大于等于maximumPoolSize,那么线程池启动拒绝策略
  3. 当一个线程完成任务,会从队列中取下一个任务来执行。
  4. 当一个线程等待时间超过keepAliveTime,线程池会进行判断
    4.1. 如果当前运行的线程数大于corePoolSize,那么这个线程停掉。
  5. 线程池的所有任务完成之后,最后会收缩到corePoolSize大小。

1.5、ThreadFactory

  1. 在线程池的七大核心参数还有一个ThreadFactory没谈到,这个参数的作用是什么?线程池中的线程是从何而来?其实线程池中的所有线程都来源于ThreadFacotry
  2. ThreadFactory就是一个接口,只有一个用来创建线程的方法,当线程池需要新建线程的时候,就会调用这方法。
    Thread newThread(Runnable r);
    

1.6、手写一个线程池

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 手写线程池
        final Integer corePoolSize = 2;
        final Integer maximumPoolSize = 5;
        final Long keepAliveTime = 1L;

        // 自定义线程池,只改变了LinkBlockingQueue的队列大小
        ExecutorService executorService = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                //ThreadFacotry使用默认的
                Executors.defaultThreadFactory(),
                //拒绝策略使用默认的
                new ThreadPoolExecutor.AbortPolicy());

        // 模拟10个用户来办理业务,每个用户就是一个来自外部请求线程
        try {
            // 循环十次,模拟业务办理,让5个线程处理这10个请求
            for (int i = 0; i < 10; i++) {
                final int tempInt = i;
                executorService.execute(() -> {
                  System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	//最后记得要关闭线程池
            executorService.shutdown();
        }
    }
}
  1. 注意点:关闭线程池,如果当前有线程正在执行,shutdown()不会立刻暴力关闭终止所有任务,会等待所有任务执行完毕之后再关闭,但并不会等待所有线程执行完成之后再返回。简单说就是:调用shutdown()方法的时候,这是一个信号,说我这个线程池不再接受其他行任务

1.7、线程池的参数选择。

  1. corePoolSize和maximumPoolSize是根据生产环境决定的。
  2. 根据具体业务配置,分为CPU密集型和IO密集型。
    2.1. CPU密集型:该任务需要大量运算,且没有阻塞,CPU火力全开。只有在真正的多核CPU才能得到加速。任务配置尽可能少的线程数量,通常为:CPU核数 + 1个线程数
    2.2. IO密集型:该任务需要大量IO操作(大量阻塞)。IO密集型任务中使用多线程可以大大的加速程序的运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。IO密集的时候,大部分线程都被阻塞,所以需要多配置线程数,公式:CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8 ~ 0.9左右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值