zoukankan      html  css  js  c++  java
  • 多线程之线程池

    什么是线程池

    在真实的生产环境中,可能需要很多的线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源,如果不对线程进行控制和管理,反而会影响程序的性能,线程开销主要包括:

    • 创建与启动线程的开销
    • 线程销毁的开销
    • 线程调度的开销
    • 线程数量受限CPU处理器数量

    ​ 线程池就是有限使用线程的一种常用方式,线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。

    image-20210404131039300

    JDK提供与线程池相关的API

    JDK提供了一套Executor框架,可以帮助开发人员有效使用线程

    image-20210404131800650

    一般使用ExecutorService的实体类Executors的实例方法来创建线程池

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadPoor {
        public static void main(String[] args) {
            //创建有五个大小的线程池
            ExecutorService fixedThreadPool =Executors.newFixedThreadPool(5);
    
            //向线程池提交18个任务
            for (int i = 0; i <18 ; i++) {
                fixedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getId()+"编号任务正在执行");
                        try {
                            Thread.sleep(2000);//模拟任务时长
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
    

    image-20210404133002781

    向线程池提交了18个任务,这18个任务都存储在阻塞队列,线程池中5个线程在阻塞队列中取5个任务执行。

    线程池的计划任务

    ExecutorService还有一个子接口ScheduledExecutorService,这一个线程池可以对任务进行调度,有计划的执行某个任务。

    package com;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ScheduledExecutorServiceText {
        public static void main(String[] args) {
            //创建一个有调度功能的线程池
            ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(10);
    
            //在延迟两秒之后执行任务
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId()+"--"+System.currentTimeMillis());
                }
            },2, TimeUnit.SECONDS);
    
            //以固定的频率执行任务 3秒以后执行 每隔2秒执行一次
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId()+"以固定频率开启任务"+System.currentTimeMillis());
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },3,2,TimeUnit.SECONDS);
    
            //在上次任务结束之后,固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后2秒再次执行
            scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId()+"在固定频率开启任务"+System.currentTimeMillis());
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },3,2,TimeUnit.SECONDS);
        }
    }
    

    image-20210404135739811

    • scheduled(Runnable任务,延迟时长,时间单位)给某一个线程固定的延迟
    • scheduleAtFixedRate(Runnable任务,延迟时长,固定频率,时间单位),如果睡眠时间超过任务等待时间,完成任务后立即执行下一个任务,如果任务等待一天,你执行耗时1.5天,超过了任务等待的时间,下一次开启不需要又等待一天,直接立即执行。
    • scheduleWithFixedDelay(Runnable任务,延迟时长,固定频率,时间单位),固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后再次执行,如果任务等待一天,你执行耗时1.5天,任务结束后你还需要等待1.5天才能执行。

    线程池的底层实现

    查看Excutors工具类中newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool源码

     public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
      //核心线程和最大线程相同,如果多出一个会放在阻塞队列中
        }
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        //这个线程核心线程和最大线程数都为1,在任意时刻只有一个线程在执行任务,如果有多个任务放在阻塞队列中
        }
    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
           //在极端情况下,每次提交新的任务都会创建新的线程执行,适合用来执行大量耗时短并且提交频繁的任务
        }
    

    Excutors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor线程池,这些方法都是ThreadPoolExecutor线程池的封装,ThreadPoolExecutor是ExecutorService的实现类。

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
        }
    

    各个参数的含义:

    • corePoolSize线程池中核心线程的数量
    • maximumPoolSize线程池中最大线程数量
    • keepAliveTime当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空闲线程在多少时间内销毁
    • unit实参单位
    • workQueue任务队列,把任务提交到该任务队列中等待执行
    • threadFactory线程工程用于创建线程
    • handler拒绝策略,当任务太多来不及处理时,如何拒绝

    说明:workQueue工作队列是指提交未执行的任务队列,它是BlockingQueue接口的对象,仅用于存储Runnable接口。根据队列功能分类,在ThreadPoorExecutor构造方法可以使用以下几种阻塞队列:

    • 直接提交队列,由SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真是保存,总是将新的任务提交给线程执行,如果没有空闲线程则尝试创建新的线程,如果线程数量已经达到maxinumpoolsize规定的最大值则执行拒绝策略。
    • 有界任务队列,由ArrayBlockingQueue实现,在创建ArrayBlockQueue对象时,可以指定一个容量,当有任务需要执行时,如果线程池种中线程数量小于CorePoolSize核心线程数则创建新的线程,如果大于核心线程数则加入等待队列,如果队列已满则无法加入,在线程数小于maxinumpoo;size指定的最大线程数前提下会创建新的线程来执行,如果线程数大于maxinumpoolsize最大线程数则执行绝策略
    • 无界任务队列,由LinkBlockQueue对象实现,与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,当有新的任务时,在系统线程数小于corepoolsize核心线程数,核心线程数则把任务加入阻塞队列
    • 优先任务队列,由priorityBlockQueue实现的,是带有任务优先级的队列,是一个特殊的无界队列,不管是ArrayBlockQueue队列还是LinkedBlockQueue队列都是按照先进先出算法处理的,在priorityBlockQueue队列中可以根据任务优先级顺序先后执行

    image-20210405220603124

    线程池的拒绝策略

    ThreadPoolExecutor方法的最后一个参数指定了拒绝策略,当提交给线程池任务量超过承载能力,即线程用完了,等待队列也满了,无法为新的提交任务服务,可以通过拒绝策略来解决问题,JDK提供了四种拒绝策略。

    线程池中定义了四个内部类都实现了拒绝策略继承了RejectedExecutionHandler接口

    image-20210405224346100

    AbortPolicy策略会抛出异常

    CallerRunsPolicy策略,只要线程池没有关闭,会调用线程中运行当前被丢弃的任务

    DiscardPolicy策略,直接丢弃这个无法处理的任务

    DiscardOldestPolicy策略,会将任务队列中最老的任务丢弃,尝试再次提交新任务

    defaultHandler是默认的拒绝策略,AbortPolicy抛出异常

     private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
    

    如果内置拒绝策略午饭满足实际需求,可以扩展RejectedExecutionHandler接口

  • 相关阅读:
    【leetcode】1365. How Many Numbers Are Smaller Than the Current Number
    【leetcode】1363. Largest Multiple of Three
    【leetcode】1362. Closest Divisors
    【leetcode】1361. Validate Binary Tree Nodes
    【leetcode】1360. Number of Days Between Two Dates
    【leetcode】1359. Count All Valid Pickup and Delivery Options
    【leetcode】1357. Apply Discount Every n Orders
    【leetcode】1356. Sort Integers by The Number of 1 Bits
    ISE应用入门的一些问题
    DDR的型号问题
  • 原文地址:https://www.cnblogs.com/cg-ww/p/14619834.html
Copyright © 2011-2022 走看看