Loading

多线程之线程池

什么是线程池

在真实的生产环境中,可能需要很多的线程来支撑整个应用,当线程数量非常多时,反而会耗尽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接口

posted @ 2021-04-05 22:45  炒焖煎糖板栗  阅读(271)  评论(0编辑  收藏  举报