美文网首页
并发——线程池

并发——线程池

作者: 四喜汤圆 | 来源:发表于2019-07-12 00:04 被阅读0次

    一、作用

    ①降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
    ②提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
    ③方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
    ④管理线程。线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

    二、相关概念

    1. 线程池相关配置

    和线程池相关的类ThreadPoolExecutor,该类的构造函数的配置。
    (1)int corePoolSize
    核心线程数。

    新建线程时,若当前线程数 x [0,corePoolSize),新建的是核心线程;若[corePoolSize,+~),新建的是非核心线程。

    核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干。
    如果指定ThreadPoolExecutorallowCoreThreadTimeOut=true,那么核心线程如果闲置超过一定时间(时长可由参数配置),就会被销毁掉。

    (2)int maximumPoolSize
    线程池中线程总数最大值(线程总数=核心线程数+非核心)

    (3)long keepAliveTime
    该线程池中非核心线程闲置超时时长。

    若一个非核心线程闲置状态超过设定时长,就会被销毁掉。如果设置allowCoreThreadTimeOut=true,该参数则会作用于核心线程。

    (4)TimeUnit unit
    keepAliveTime 的单位,TimeUnit 是一个枚举类型,包括:
    NANOSECONDS:1微毫秒
    MICROSECONDS:1微妙
    MILLISECONDS:1毫秒
    SECONDS:1秒
    MINUTES:1分钟
    HOURS:1小时
    DAYS:1天

    (5)BlockingQueue workQueue
    线程池中的任务队列:维护者等待执行的 Runnable 对象。

    所有的线程都在干活时,新添加的任务就会被添加到这个队列中等待出来,如果队列满了,则新建非核心线程执行任务。

    • SynchronousQueue
      这个队列接到任务时会直接提交给线程处理,如果此时线程池中的线程都在工作,那就新建一个线程来处理这个任务。

    为了保证不会出现『线程数达到了 maximumPoolSize』的错误,使用这个类型队列时,maximumPoolSize=Integer.MAX_VALUE

    • LinkedBlockingQueue
      收到任务时:

    • ArrayBlockingQueue
      当收到任务时,
      若核心线程数<corePoolSize,则新建核心线程完成任务;
      若当前线程数=corePoolSize,则将任务移入队列等待;
      若队列已满,新建非核心线程执行任务;
      若线程总数=maximumPoolSize,抛出异常。

    • DelayQueue

    区别

    2. 线程池执行任务时遵循的规则

    (1)如果线程池中线程数量未达到核心线程数量,那么直接启动一个核心线程来执行任务。
    (2)如果线程池中线程数量已达到核心线程数量,那么任务会被插入到任务队列中排队等待执行。
    (3)如果(2)中任务无法插入到队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
    (4)如果(3)中线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。

    【一篇能够帮你理清线程池的文章】https://mp.weixin.qq.com/s/8OPonUkTbHwRI8mrMig58A

    3. 常见线程池

    通过 Executors 可以创建四类线程池。

    (1)FiexThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
            0 L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue < Runnable > ());
    }
    
    • 核心线程数
      只有核心线程

    • 线程池能容纳的最大线程数

    • 线程闲置时的超时时间
      这些核心线程没有超时机制。
      该线程池只有核心线程,且这些核心线程不会被回收,意味着它能快速响应外界请求。

    • 任务队列
      任务队列没有大小限制

    (2)CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60 L, TimeUnit.SECONDS,
            new SynchronousQueue < Runnable > ());
    }
    
    • 核心线程数
      没有核心线程

    • 线程池能容纳的最大线程数
      无限大

    • 线程闲置时的超时时间
      该线程池只有非核心线程,这些线程的超时时间是 60s。

    当线程池中线程都处于活动状态时,线程池会创建新的线程处理任务,否则利用空闲线程来处理新任务。

    • 任务队列
      该任务队列无法插入任务。这将导致任务会立即被执行,该线程池适合执行大量的耗时较少的任务。

    (3)ScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
            DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
            new DelayedWorkQueue());
    }
    
    • 核心线程数
      核心线程数固定,非核心线程数没有限制。

    • 线程池能容纳的最大线程数
      无限大

    • 线程闲置时的超时时间
      当非核心线程被闲置10毫秒后,会被立即回收。

    • 任务队列
      主要用于执行定时任务和有固定周期的重复任务

    (4)SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
            0 L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue < Runnable > ()));
    }
    
    • 核心线程数
      该线程池中只有一个核心线程。它确保了所有的任务都在一个线程中按顺序执行。这使得任务间不需要处理线程同步问题。

    • 线程池能容纳的最大线程数

    • 线程闲置时的超时时间

    • 任务队列
      无限大

    三、使用

    1. 通过 ThreadPoolExecutor 创建一个线程池

    (1)创建线程

    // 新建线程池
    ExecutorService service = new ThreadPoolExecutor(5, 10, 10, TimeUnit.HOURS, new LinkedBlockingDeque < Runnable > ());
    

    (2)提交任务
    execute()submit()两种方法。

    // 使用execute向线程池提交任务
    service.execute(new Runnable() {
        public void run() {
    
        }
    });
    
    // 使用submit提交任务
    Future < Integer > future = service.submit(new Callable < Integer > () {
        @Override
        public Integer call() throws Exception {
            return 2;
        }
    });
    try {
        Integer num = future.get();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    (3)线程池关闭

    // 关闭线程池
    service.shutdown();
    
    service.shutdownNow();
    

    2. 直接使用现有的4种线程池类创建线程池

    ExecutorService tp = Executors.newFixedThreadPool(4);
    ExecutorService tp2 = Executors.newCachedThreadPool();
    ExecutorService tp3 = Executors.newScheduledThreadPool(4);
    ExecutorService tp4 = Executors.newSingleThreadExecutor();
    

    3.建议:为线程池命名

    阿里规约

    四、重点解析

    1.Q:线程池中核心线程是如何复用的

    陈小缘

    2.Q:自己写线程池

    参考文献

    lrh_Java线程池
    线程池,这一篇或许就够了

    相关文章

      网友评论

          本文标题:并发——线程池

          本文链接:https://www.haomeiwen.com/subject/ivndkctx.html