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接口

  • 相关阅读:
    Codeforces Round #613 选讲
    Codeforces Round #612 选讲
    Codeforces917E
    一道题20
    LOJ#2244. 「NOI2014」起床困难综合症
    求欧拉回路
    *LOJ#2134. 「NOI2015」小园丁与老司机
    vim操作命令
    常见问题解决
    CentOS7下如何修改mysql的数据目录
  • 原文地址:https://www.cnblogs.com/cg-ww/p/14619834.html
Copyright © 2011-2022 走看看