Java 系列 - ThreadPool 线程池

目的:掌握线程池的使用,API中各个参数

什么是线程池: 线程池是一种线程使用模式,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

为什么使用线程池 : 为了减少创建和销毁线程的次数,让每个线程都可以多次的使用,可以根据系统情况调整线程的数量,防止消耗过多内存。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,使用线程池就可以优化。

说白了就是别再使用 implements Runnable, extends Thread, implements Callable 这三种基本方式去创建线程了,使用线程池这个类来管理线程的初始化参数,创建,销毁

ThreadPool 核心参数

Java-JDK ThreadPool 源码中,构造器中的核心参数如下

public ThreadPoolExecutor(
   /** 核心线程数 */
  int corePoolSize,
  /** 最大线程数 */
  int maximumPoolSize,
  /** 线程空闲时间 */
  long keepAliveTime,
  /** 时间单位 */
  TimeUnit unit,
  /** 任务队列 */
  BlockingQueue<Runnable> workQueue,
  /** 线程工厂 */
  ThreadFactory threadFactory,
  /** 拒绝策略 */
  RejectedExecutionHandler handler
)

使用线程池时,主要关注 workQueue ,threadFactory , handler 这三个参数

jdk 自带的四种线程池创建方式

使用 JDK 封装好的类来创建线程池非常简单,它们都是在 ThreadPoolExecutor 基础上进行了一层抽象封装

// 第一种线程池:固定个数的线程池,可以为每个CPU核绑定一定数量的线程数
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
// 缓存线程池,无上限
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 单一线程池,永远会维护存在一条线程
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 固定个数的线程池,可以执行延时任务,也可以执行带有返回值的任务。
ScheduledExecutorService scheduledThreadPool = ExecutorsnewScheduledThreadPool(5);

但是我们通常都不选择 JDK 提供的这四种,而是使用自定义的方式来创建线程池,因为这样更灵活,可以随意设置池数量参数和拒绝策略

ExecutorService executorService = new ThreadPoolExecutor(0,
    10,
    1000,
    TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);

executorService.execute(task);

workQueue:任务队列

在线程池的构造方法中,任务队列是第四个参数。

任务队列,它一般分为:直接提交队列、有界任务队列、无界任务队列、优先任务队列

  1. 直接提交队列

直接提交队列使用 SynchronousQueue 来创建

SynchronousQueue 是一个特殊的 BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,然后需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作

下面的示例中,使用SynchronousQueue创建了一个核心线程数为 0,最大线程数为 2 的固定线程数量的线程池。在执行任务时,由于任务是耗时操作并且任务数量为3(循环了3次)大于2,因此直接提交队列
对于超过最大线程池数量的情况,会直接执行拒绝策略 RejectedExecutionHandler。这里的拒绝策略是 AbortPolicy表示直接抛出异常。最终打印结果就是: 抛出异常+执行 2 次任务

public class FixThreadPool {

  // 1. 创建线程池(执行器)
  public static ExecutorService executorService = new ThreadPoolExecutor(
    0,
    2,
    60,
    TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
  );

  public static void main(String[] args) {
    // 2.任务
    Runnable task = new Runnable() {
      @Override
      public void run() {
          try {
              // 耗时操作,休眠3秒
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("执行任务");
      }
    };

    for (int i = 0; i < 10; i++) {
      // 3.执行
      executorService.execute(task);
    }
  }
}

执行结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.ifdom.thread.creatThreadPool4Mode.FixThreadPool$1@3941a79c rejected from java.util.concurrent.ThreadPoolExecutor@506e1b77[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.ifdom.thread.creatThreadPool4Mode.FixThreadPool.main(FixThreadPool.java:42)
执行任务
执行任务
  1. 有界任务队列
  1. 如有新任务需要执行,并且已创建线程数量没有达到最大线程数,那么会创建新线程去执行.直到创建的新线程数超过 有界任务 ArrayBlockingQueue所设定容量,然后执行拒绝策略
  2. 线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在 corePoolSize 以下,反之当任务队列已满时,则会以 maximumPoolSize 为最大线程数上限。
public class WorkQueue4Mode {
    public static void main(String[] args) {

        // 2.有界执行队列
        ExecutorService executorService2 = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService2.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:" + finalI);
            });
        }
    }
}
  1. 无界执行队列

使用 LinkedBlockingQueue 创建

无界执行队列意味着参数 maximumPoolSize 失效了。

使用这种模式需要注意的是 缓存的队列任务可能会由于太多而导致系统资源耗尽

public class WorkQueue4Mode {
    public static void main(String[] args) {
        // 3.无界执行队列
        ExecutorService executorService3 = new ThreadPoolExecutor(1,
                2,
                300,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 执行任务  10W个任务
        for (int i = 0; i < 100000; i++) {
            int finalI = i;
            executorService3.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("执行任务:" + finalI);
            });
        }
    }
}
  1. 优先级队列

在其他三种队列里,队列中待执行的任务是没有优先级的,因此 cpu 空闲后,随机选中待执行线程中的任务进行执行,而优先级队列允许我们指定操作系统来优先执行特定任务

public class WorkQueue4Mode {
    public static void main(String[] args) {
        // 4.优先任务队列
        ExecutorService executorService4 = new ThreadPoolExecutor(1,
                2,
                1000,
                TimeUnit.SECONDS,
                new PriorityBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 执行任务
        for (int i = 0; i < 10; i++) {
            executorService4.execute(new PriorityTaskHandle(i));
        }
    }
}
/**
 * 定义一个有优先级参数的任务
 * 示例:定义一个任务,通过传参传入优先级,如果当前任务优先级大于一,那么将将其优先级设定为传入参数
 **/
class PriorityTaskHandle implements Runnable, Comparable<PriorityTaskHandle> {
    private int priority;

    public PriorityTaskHandle() {
    }

    public PriorityTaskHandle(int priority) {
        this.priority = priority;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    /**
    * 设定优先级
    * 数值越小,优先级越高
    **/
    @Override
    public int compareTo(PriorityTaskHandle o) {
        return this.priority > o.priority ? -1 : 1;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行任务:" + this.priority);
    }
}

示例结果: 任务队列会在第一次创建线程池时,缓存所有任务,只有这样才能比较他们之间的优先级

执行任务:0
执行任务:9
执行任务:8
执行任务:7
执行任务:6
执行任务:5
执行任务:4
执行任务:3
执行任务:2
执行任务:1

ThreadFactory:线程工厂

线程工厂一般用默认即可:Executors.defaultThreadFactory()

RejectedExecutionHandler: 拒绝策略

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定合理的拒绝策略。ThreadPoolExecutor 自带的拒绝策略如下:

  • AbortPolicy: 该策略会直接抛出异常,阻止系统正常工作。 如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现

  • DiscardPolicy:丢弃任务,但是不抛出异常。 如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。业务场景中需允许任务的丢失,那么使用此策略;

  • DiscardOldestPolicy:丢弃队列最老的任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务;然后重新尝试提交被拒绝的任务

  • CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。我在做短信平台时,线程池采用了该策略,因为我们的短信一条都不能漏发,每个任务必须处理。

RejectedExecutionHandler:线程池的执行顺序

  • 当线程数小于核心线程数时,创建线程。
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建线程。
  • 若线程数等于最大线程数,则执行拒绝策略

线程池的几种状态

在源码中,你会看到这四个奇怪的值,他们表示线程池状态

// runState is stored in the high-order bits
//1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING    =  2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED =  3 << COUNT_BITS;
状态值描述
RUNNING运行状态,能够接受新的任务且会处理阻塞队列中的任务
SHUTDOWN关闭状态,不接受新任务,但是会处理阻塞队列中的任务,执行线程池的 shutDown()
STOP停止状态,不接受新的任务,也不会处理等待队列中的任务并且会中断正在执行的任务,调用的是线程池的方法 shutDownNow()
TIDYING整理状态,即所有的任务都停止了,线程池中线程数量等于 0,会执行钩子函数 terminated()。若用户想在线程池变为 TIDYING 时,进行相应的处理;可以通过重载 terminated()函数来实现
TERMINATED结束状态,terminated() 方法执行完
拓展:ThreadPoolTaskExecutorThreadPoolExecutor 有何区别?
  • ThreadPoolTaskExecutor 是 spring core 包中的,而 ThreadPoolExecutor 是 JDK 中的 JUC。
  • ThreadPoolTaskExecutor 是对 ThreadPoolExecutor 进行了封装处理。
拓展:优先级队列 PriorityQueue 源码

PriorityQueue 内部通过创建 new ReentrantLock() 可重入锁,来控制执行队列任务的锁竞争。

  1. 扩容: 尝试扩容,容量以达到至少可以新增一个以上的元素(但通常扩展约 50%)为准,放弃(允许重试)处于竞争(我们预计这种情况很少发生)。只能在持有锁的情况下扩容
  private void tryGrow(Object[] array, int oldCap) {
      lock.unlock(); // 必须先释放锁,稍后再持有锁
      Object[] newArray = null;
      if (allocationSpinLock == 0 &&
          UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                    0, 1)) {
          try {
              int newCap = oldCap + ((oldCap < 64) ?
                                      (oldCap + 2) : // grow faster if small
                                      (oldCap >> 1));
              if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                  int minCap = oldCap + 1;
                  if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                      throw new OutOfMemoryError();
                  newCap = MAX_ARRAY_SIZE;
              }
              if (newCap > oldCap && queue == array)
                  newArray = new Object[newCap];
          } finally {
              allocationSpinLock = 0;
          }
      }
      if (newArray == null) // back off if another thread is allocating
          Thread.yield();
      lock.lock(); // 再次持有锁
      if (newArray != null && queue == array) {
          queue = newArray;
          System.arraycopy(array, 0, newArray, 0, oldCap);
      }
  }
  1. 比较
    /**
    * 二分法查找
    * k: 队列容量
    * x: 目标元素
    * array:  queue 队列
    * cmp: 比较器
    *
    * >>> : 右移一位,无符号位,高位填0
    * >> : 右移一位,有符号位,高位补符号位
    **/
private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
                                    Comparator<? super T> cmp) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (cmp.compare(x, (T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = x;
}
拓展:在 Spring 中封装位工具类

封装:

package com.springconfig.config;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
 * @Author ifredomvip@gmail.com
 **/
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数目
        executor.setCorePoolSize(i * 2);
        //指定最大线程数
        executor.setMaxPoolSize(i * 2);
        //队列中最大的数目
        executor.setQueueCapacity(i * 2 * 10);
        //线程名称前缀
        executor.setThreadNamePrefix("ThreadPoolTaskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //当调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //线程空闲后的最大存活时间
        executor.setKeepAliveSeconds(60);
        //加载
        executor.initialize();
        System.out.println("初始化线程池成功");
        return executor;
    }

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        //获取cpu核心数
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数
        int corePoolSize = i * 2;
        //最大线程数
        int maximumPoolSize = i * 2;
        //线程无引用存活时间
        long keepAliveTime = 60;

        TimeUnit unit = TimeUnit.SECONDS;

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(i * 2 * 10);

        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //拒绝执行处理器
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
        //创建线程池
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
}

使用:

@RestController
public class BookController {

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @GetMapping
    public void getCity() {
        threadPoolTaskExecutor.execute(()->{
            System.out.println("执行任务");
        });
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值