线程池

    技术2022-07-11  95

    目录

    概念优点架构线程池三种常用创建方式newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool 线程池底层原理线程池工作流程线程池的拒绝策略 自定义线程池

    概念

    线程池主要是控制运行线程的数量,将待处理任务放到等待队列,然后创建线程执行这些任务。如果超过了最大线程数,则等待

    优点

    线程复用:不用一直new新线程,重复利用已经创建的线程来降低线程的创建和销毁开销,节省系统资源。提高响应速度:当任务达到时,不用创建新的线程,直接利用线程池的线程。管理线程:可以控制最大并发数,控制线程的创建等

    架构

    工具类ArrayArraysCollectionCollectionsExecutorExecutors

    线程池三种常用创建方式

    newFixedThreadPool

    使用LinkedBlockingQueue实现,定长线程池

    public static void main(String[] args) { //一池5个线程 ExecutorService threadPool = Executors.newFixedThreadPool(5); //一般常用try-catch-finally //模拟10个用户来办理业务,每个用户就是一个线程 try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭线程池 threadPool.shutdown(); } }

    newSingleThreadExecutor

    使用LinkedBlockingQueue实现,一池只有一个线程

    public static void main(String[] args) { //一池1个线程 ExecutorService threadPool = Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }

    newCachedThreadPool

    使用SynchronousQueue实现,变长线程池

    public static void main(String[] args) { //不定量线程 ExecutorService threadPool = Executors.newCachedThreadPool(); try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }

    线程池底层原理

    其实我们看常见的三中常见创建线程池的三个源码里面都是创建了ThreadPoolExecutor对象,而且都传入了一个阻塞队列

    参数意义corePoolSize线程池常驻核心线程数maximumPoolSize能够容纳的最大线程数keepAliveTime多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止unit存活时间单位workQueue任务队列,被提交但尚未被执行的任务threadFactory生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可handler拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何来拒绝

    corePoolSize就像银行的“当值窗口“,比如今天有2位柜员在受理客户请求(任务)。如果超过2个客户,那么新的客户就会在等候区(等待队列workQueue)等待。当等候区也满了,这个时候就要开启加班窗口,让其它3位柜员来加班,此时达到最大窗口maximumPoolSize,为5个。如果开启了所有窗口,等候区依然满员,此时就应该启动”拒绝策略“handler,告诉不断涌入的客户,叫他们不要进入,已经爆满了。由于不再涌入新客户,办完事的客户增多,窗口开始空闲,这个时候就通过keepAlivetTime将多余的3个”加班窗口"取消,恢复到2个”当值窗口"

    线程池工作流程

    线程池的拒绝策略

    当等待队列满时,且达到最大线程数,再有新任务到来,就需要启动拒绝策略。

    JDK提供了四种拒绝策略,均实现了RejectedExecutionHandler接口,分别是。

    AbortPolicy:默认的策略,直接抛出RejectedExecutionException异常,阻止系统正常运行。CallerRunsPolicy:既不会抛出异常,也不会终止任务,而是将任务返回给调用者。DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交任务。DiscardPolicy:直接丢弃任务,不做任何处理。

    自定义线程池

    实际生产环境其实都不用,而是自定义线程池

    FixedThreadPool和SingleThreadExecutor允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求导致OOMCachedThreadPool和ScheduledThreadPool允许的创建线程的最大数量为Integer.MAX_VALUE,可能会创建大量的线程导致OOM private ExecutorService customThreadPool() { ExecutorService threadPool = new ThreadPoolExecutor(0, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy() ); return threadPool; }

    那怎么设置最合适的线程数

    先看一下核数 System.out.println(Runtime.getRuntime().availableProcessors()); cpu密集型,尽可能配置少的线程数数量 公式: cpu核数+1个线程的线程池io密集型 io密集型,任务需要大量的io,也就是大量的阻塞,大部分线程都阻塞,所以尽可能配置多的线程数量,有两种办法 3.1 cpu核数*2 3.2 cpu核数/ (1 - 阻塞系数),阻塞系数一般是0.8-0.9 比如8和,8/(1-0.9)=80个线程数量
    Processed: 0.012, SQL: 9