Java线程池攻略<二>:四种常用线程池剖析

214 阅读6分钟

前言


Java线程池攻略<一>:初识ThreadPoolExecutor
Java线程池攻略<二>:四种常用线程池剖析

基于ThreadPoolExecutor共有四种类型线程池

  • FixedThreadPool:固定数量线程池
  • SingleThreadExecutor:单线程线程池
  • CachedThreadPool:缓存线程池
  • ScheduledThreadPoolExecutor:调度线程池

image.png

FixedThreadPool


固定数量线程池:newFixedThreadPool()

    //被称为可重用固定线程数的线程
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

示例

    public class DemoFixedThreadPool {

        public static void main(String[] args) {

            ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

            // 创建需执行的任务
            Runnable task = () -> {
                LocalDateTime date=LocalDateTime.now();
                System.out.println(" 当前时间为:"+ date);
            };

            // 线程池添加任务
            fixedThreadPool.execute(task);

            //关闭线程池
            fixedThreadPool.shutdown();

            try {
                fixedThreadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

特性

  1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
  2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 LinkedBlockingQueue
  3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行

为什么不推荐使用FixedThreadPool

FixedThreadPool 使用无界队列 LinkedBlockingQueue(队列的容量为 Integer.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :

  1. 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize
  2. 由于使用无界队列时 maximumPoolSize 将是一个无效参数,因为不可能存在任务队列满的情况。所以,

通过创建 FixedThreadPool的源码可以看出创建的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 被设置为同一个值。

  1. 由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数;
  2. 运行中的 FixedThreadPool(未执行 shutdown()或 shutdownNow())不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。

SingleThreadExecutor


单线程线程池:newSingleThreadExecutor()

    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

示例

    public class DemoSingleThreadPool {

        public static void main(String[] args) {
            ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

            // 创建需执行的任务
            Runnable task = () -> {
                LocalDateTime date=LocalDateTime.now();
                System.out.println(" 当前时间为:"+ date);
            };

            // 线程池添加任务
            singleThreadExecutor.execute(task);

            //关闭线程池
            singleThreadExecutor.shutdown();

            try {
                singleThreadExecutor.awaitTermination(10000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

CachedThreadPool

缓存线程池: newCachedThreadPool()

        public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

示例

    public class DemoCacheThreadPool {

        public static void main(String[] args) {
            ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

            // 创建需执行的任务
            Runnable task = () -> {
                LocalDateTime date=LocalDateTime.now();
                System.out.println(" 当前时间为:"+ date);
            };

            // 线程池添加任务
            cachedThreadPool.execute(task);

            //关闭线程池
            cachedThreadPool.shutdown();

            try {
                cachedThreadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

特性

CachedThreadPool 的corePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE,空闲线程的等待时间为60秒

即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,

CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源

ScheduledThreadPoolExecutor

调度线程池:newScheduledThreadPool()

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

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

示例

    public class DemoScheduleThreadPool {

        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

            // 创建需执行的任务
            Runnable task = () -> {
                LocalDateTime date=LocalDateTime.now();
                System.out.println(" 当前时间为:"+ date);
            };

            // 线程池添加任务  每4秒执行一次
            scheduledExecutorService.scheduleAtFixedRate(task, 5L, 4L, TimeUnit.SECONDS);

            //关闭线程池, 不能关闭
            //scheduledExecutorService.shutdown();
        }

    }

特性

image.png

线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指 ScheduledFutureTask的 time 大于等于当前系统的时间;

  1. 线程 1 执行这个 ScheduledFutureTask;
  2. 线程 1 修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间;
  3. 线程 1 把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。

线程池参数确定

有一个简单并且适用面比较广的公式:

  • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
  • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

如何判断是 CPU 密集任务还是 IO 密集任务?

CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。

线程工作源码

public class ThreadPoolExecutor extends AbstractExecutorService {
   
    // 全局锁,并发操作必备
    private final ReentrantLock mainLock = new ReentrantLock();
   
    // 跟踪线程池的最大大小,只有在持有全局锁mainLock的前提下才能访问此集合
    private int largestPoolSize;
    
    // 工作线程集合,存放线程池中所有的(活跃的)工作线程,只有在持有全局锁mainLock的前提下才能访问此集合
    private final HashSet<Worker> workers = new HashSet<>();
    
    //阻塞队列
    private final BlockingQueue<Runnable> workQueTue;
  
    // 获取线程池状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }

    // ctl 中保存的线程池当前的一些状态信息
    // ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 
    // 它包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),
    // 这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,
    // CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
    // 线程池执行任务 
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        // ctl 中保存的线程池当前的一些状态信息, ctl类型:AtomicInteger
        int c = ctl.get();
        //  下面会涉及到 3 步 操作
        // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
        // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;
        // 然后,启动该线程从而执行任务。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 2.如果当前执行的任务数量大于等于 corePoolSize 的时候就会走到这里
        // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态并且队列可以加入任务,
        // 该任务才会被加入进去
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,
            // 并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0
                 //获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
                 //这里传入的参数表示:
                 //1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
                 //2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
                 //如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行                 
                 addWorker(null, false);
        }
        // 3.通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        // 如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容
        else if (!addWorker(command, false))
            reject(command);
    }
    
   /**
    * 添加新的工作线程到线程池
    * @param firstTask 要执行
    * @param core参数为true的话表示使用线程池的基本大小,为false使用线程池最大大小
    * @return 添加成功就返回true否则返回false(同时会执行线程 Thread.start() 方法,启动线程)
    */
   private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            //这两句用来获取线程池的状态
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
               //获取线程池中工作的线程的数量
                int wc = workerCountOf(c);
                // core参数为true的话表明队列也满了,线程池大小变为 maximumPoolSize
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 原子操作将workcount的数量加1
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 如果线程的状态改变了就再次执行上述操作
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        // 标记工作线程是否启动成功
        boolean workerStarted = false;
        // 标记工作线程是否创建成功
        boolean workerAdded = false;
        Worker w = null;
       
        try {         
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 获取线程池状态
                    int rs = runStateOf(ctl.get());
                    // rs < SHUTDOWN 如果线程池状态依然为RUNNING,并且线程的状态是存活的话,就会将工作线程添加到工作线程集合中
                    // (rs=SHUTDOWN && firstTask == null)如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,
                    // 同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
                    // firstTask == null证明只新建线程而不执行任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        // 更新当前工作线程的最大容量
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 工作线程是否启动成功
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                // 如果成功添加工作线程,则调用Worker内部的线程实例t的Thread#start()方法启动真实的线程实例
                if (workerAdded) {
                    //这里会开启线程
                    t.start();
                    // 标记线程启动成功
                    workerStarted = true;
                }
            }
        } finally {
           // 线程启动失败,需要从工作线程中移除对应的Worker
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
        
}