线程池创建常用方式以及核心参数解析

面试题

平时怎么使用多线程?哪些好处?线程池的几个核心参数的意义?

线程池概念

  • 简介

    顾名思义,管理线程的池子

  • 好处

    1. 降低线程创建和线程销毁造成的开销

    2. 提高响应速度。任务到达时,相对于手工创建一个线程,直接从线程池中拿线程,速度肯定要快很多

    3. 提高线程可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以统一进行分配、调优和监控

线程池创建

  • 创建线程池底层均会调用ThreadPoolExecutor构造函数

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

    参数介绍:

    1. corePoolSize:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量。线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。

    2. maximumPoolSize:线程池中运行最大线程数(包括核心线程和非核心线程)

    3. keepAliveTime:线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间。当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用。

    4. unit:存活时间单位,与keepAliveTime搭配使用(TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS)

    5. workQueue:存放任务的阻塞队列(维护着等待执行的 Runnable对象。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务)。

    6. handler:线程池饱和策略

常见典型工作队列

  • ArrayBlockingQueue

    使用数组实现的有界阻塞队列,特性是先进先出

  • LinkedBlockingQueue

    使用链表实现的阻塞队列,特性是先进先出,可以设置其容量,默认为Integer.MAX_VALUE

  • PriorityBlockingQueue

    使用平衡二叉树堆,实现的具有优先级的无界阻塞队列

  • DelayQueue

    无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素

  • SynchronousQueue

    不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

线程池执行流程

  • 执行流程图
    线程池执行流程图

  • 线程池处理新提交任务流程

    1. 判断线程池中核心线程数是否已达阀值corePoolSize,若否,则创建一个新核心线程执行任务

    2. 若核心线程数已达阀值corePoolSize,判断阻塞队列workQueue是否已满,若未满,则将新任务添加进阻塞队列

    3. 若已满,判断线程池中线程数是否达到阀值maximumPoolSize,若否,则新建一个非核心线程执行任务。若达到阀值,则执行线程池饱和策略

  • 线程池饱和策略

    1. AbortPolicy:直接抛出一个异常阻止系统正常运行,默认策略

    2. DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常(如果允许任务丢失,建议此方案)

    3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

    4. CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量(主线程中执行任务)

  • 结构角度执行流程图

常见典型线程池

SingleThreadExecutor(单线程池)

  • 源码

    
    
        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
    
    
    
  • 源码分析

    SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,使用无界队列LinkedBlockingQueue作为线程池的工作队列

  • 适用场景

    适用于需要保证顺序地执行各个任务,并且在任意时间点,不会有多个线程是活动的应用场景。适用串行执行场景

  • 执行流程图
    SingleThreadExecutor执行流程图

    1. 当线程池中没有线程时,会创建一个新的线程来执行任务

    2. 当前线程池中有一个线程后,将新任务加入LinkedBlockingQueue

    3. 线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行

FixedThreadPool(固定大小线程池)

  • 源码

    
        public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(),
                                        threadFactory);
        }
    
    
    
  • 源码分析

    corePoolSize等于maximumPoolSize,所以线程池中只有核心线程,使用无界队列LinkedBlockingQueue作为线程池的工作队列

    FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。

  • 适用场景

    适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的减少分配线程,适用执行长期的任务

  • 执行流程图
    FixedThreadPool执行流程

    1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务

    2. 在线程达到corePoolSize后,将新任务放到LinkedBlockingQueue阻塞队列中

    3. 线程执行完(1)中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行

CachedThreadPool(可缓存线程池)

  • 源码

    
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                        60L, TimeUnit.SECONDS,
                                        new SynchronousQueue<Runnable>());
        }
    
    
    
  • 源码分析

    核心线程数为0,总线程数量阀值为Integer.MAX_VALUE,即可以创建无限的非核心线程,使用SynchronousQueue不存储元素的阻塞队列

  • 使用场景

    执行大量短生命周期任务。因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程。每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务

  • 执行流程图
    CachedThreadPool执行流程图

    1. 先执行SynchronousQueue的offer方法提交任务,并查询线程池中是否有空闲线程来执行SynchronousQueue的poll方法移除的任务。如果有,则配对成功,将任务交给这个空闲线程

    2. 否则,配对失败,创建新的线程去处理任务

    3. 当线程池中的线程空闲时,会执行SynchronousQueue的poll方法等待执行SynchronousQueue中新提交的任务。若等待时间超过60s,空闲线程就会终止

ScheduledThreadPool(定时线程池)

  • 源码

    
    
        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
                return new ScheduledThreadPoolExecutor(corePoolSize);
        }
        
        public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                new DelayedWorkQueue());
        }
    
    
    
    
    
  • 源码分析

    线程总数阀值为Integer.MAX_VALUE,工作队列使用DelayedWorkQueue,非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程

  • 提交任务方式

    1. scheduleAtFixedRate:按照固定速率周期执行

    2. scheduleWithFixedDelay:上个任务延迟固定时间后执行

  • 使用场景

    周期性执行任务,并且需要限制线程数量的场景

  • 执行流程图

线程池执行线程任务步骤

调用Executors类的静态方法创建ExecutorService对象

  • 创建ExecutorService源码

    
        //ExecutorService是一个工厂类,主要用来创建ExecutorService
        public static ExecutorService newFixedThreadPool(int nThreads) {
    
            //用newFixedThreadPool()这个静态方法用来创建一个可重用固定线程数的线程池
            return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
        }
    
    
    
    

创建一个类实现Runnable接口

  • 案例代码

    
    
        // 创建一个实现Runnable接口的类 Thread1
        class Thread1 implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i <= 50; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + "的i值为: " + i);
                    }
                }
            }
        }
    
        // 创建一个实现Runnable接口的类 Thread2
        class Thread2 implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i <= 50; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + "的i值为: " + i);
                    }
                }
            }
        }
    
    
    
    

调用ExecutorService对象的execute()方法执行Runnable接口实例

调用ExecutorService对象的shutdown()方法关闭线程池

  • 案例源码

    
        public class ThreadPool {
    
            public static void main(String[] args) {
                // 创建一个固定线程数的的线程池
                ExecutorService pool = Executors.newFixedThreadPool(10);
    
                // 向线程中提交两个任务,执行指定的线程的操作。需要提供实现Runnable接口实现类的对象
                pool.execute(new Thread1());
                pool.execute(new Thread2());
    
                // 关闭线程池
                pool.shutdown();
            }
        }
    
    
    
    
    
  • 输出结果

    
        ​
        pool-1-thread-2的i值为: 1
        pool-1-thread-2的i值为: 3
        pool-1-thread-2的i值为: 5
        pool-1-thread-2的i值为: 7
        pool-1-thread-2的i值为: 9
        pool-1-thread-2的i值为: 11
        pool-1-thread-2的i值为: 13
        pool-1-thread-2的i值为: 15
        pool-1-thread-2的i值为: 17
        pool-1-thread-2的i值为: 19
        pool-1-thread-2的i值为: 21
        pool-1-thread-2的i值为: 23
        pool-1-thread-2的i值为: 25
        pool-1-thread-2的i值为: 27
        pool-1-thread-2的i值为: 29
        pool-1-thread-2的i值为: 31
        pool-1-thread-2的i值为: 33
        pool-1-thread-2的i值为: 35
        pool-1-thread-2的i值为: 37
        pool-1-thread-2的i值为: 39
        pool-1-thread-2的i值为: 41
        pool-1-thread-2的i值为: 43
        pool-1-thread-2的i值为: 45
        pool-1-thread-2的i值为: 47
        pool-1-thread-2的i值为: 49
        pool-1-thread-1的i值为: 0
        pool-1-thread-1的i值为: 2
        pool-1-thread-1的i值为: 4
        pool-1-thread-1的i值为: 6
        pool-1-thread-1的i值为: 8
        pool-1-thread-1的i值为: 10
        pool-1-thread-1的i值为: 12
        pool-1-thread-1的i值为: 14
        pool-1-thread-1的i值为: 16
        pool-1-thread-1的i值为: 18
        pool-1-thread-1的i值为: 20
        pool-1-thread-1的i值为: 22
        pool-1-thread-1的i值为: 24
        pool-1-thread-1的i值为: 26
        pool-1-thread-1的i值为: 28
        pool-1-thread-1的i值为: 30
        pool-1-thread-1的i值为: 32
        pool-1-thread-1的i值为: 34
        pool-1-thread-1的i值为: 36
        pool-1-thread-1的i值为: 38
        pool-1-thread-1的i值为: 40
        pool-1-thread-1的i值为: 42
        pool-1-thread-1的i值为: 44
        pool-1-thread-1的i值为: 46
        pool-1-thread-1的i值为: 48
        pool-1-thread-1的i值为: 50
    
    
    

其他

编码规范

  • 阿里巴巴编码规范不推荐用Executors来创建ThreadPoolExecutor

    以创建newFixedThreadPool为例

    
        public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>(),
                                        threadFactory);
        }
    
    
    

    其中里面设置的corePoolSize(核心线程池大小)和maximumPoolSize(最大线程数)都是nThreads,其设定的阻塞队里是无界的,则饱和策略将失效,所有请求将一直排队等待被执行,可能会产生内存溢出的风险。

    
        //手动创建线程池
        ExecutorService poolExecutor = new ThreadPoolExecutor(10, 15, 24, TimeUnit.HOURS, new LinkedBlockingDeque<>(10));
    
    

ThreadPoolExecutor源码解析

  • 继承结构图
    ThreadPoolExecutor继承结构图

  • 主要成员变量

    
    
        public class ThreadPoolExecutor extends AbstractExecutorService {
    
                private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
                // 表示线程池数量的位数,很明显是29,Integer.SIZE=32
                private static final int COUNT_BITS = Integer.SIZE - 3;
                // 表示线程池最大数量,2^29 - 1
                private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
            
                // runState is stored in the high-order bits
                private static final int RUNNING    = -1 << COUNT_BITS;
                private static final int SHUTDOWN   =  0 << COUNT_BITS;
                private static final int STOP       =  1 << COUNT_BITS;
                private static final int TIDYING    =  2 << COUNT_BITS;
                private static final int TERMINATED =  3 << COUNT_BITS;
            
                // Packing and unpacking ctl
                private static int runStateOf(int c)     { return c & ~CAPACITY; }
                private static int workerCountOf(int c)  { return c & CAPACITY; }
                private static int ctlOf(int rs, int wc) { return rs | wc; }
            
                /*
                * Bit field accessors that don't require unpacking ctl.
                * These depend on the bit layout and on workerCount being never negative.
                */
            
                private static boolean runStateLessThan(int c, int s) {
                    return c < s;
                }
            
                private static boolean runStateAtLeast(int c, int s) {
                    return c >= s;
                }
            
                private static boolean isRunning(int c) {
                    return c < SHUTDOWN;
                }
            
                /**
                * Attempts to CAS-increment the workerCount field of ctl.
                */
                private boolean compareAndIncrementWorkerCount(int expect) {
                    return ctl.compareAndSet(expect, expect + 1);
                }
            
                /**
                * Attempts to CAS-decrement the workerCount field of ctl.
                */
                private boolean compareAndDecrementWorkerCount(int expect) {
                    return ctl.compareAndSet(expect, expect - 1);
                }
            
                /**
                * Decrements the workerCount field of ctl. This is called only on
                * abrupt termination of a thread (see processWorkerExit). Other
                * decrements are performed within getTask.
                */
                private void decrementWorkerCount() {
                    do {} while (! compareAndDecrementWorkerCount(ctl.get()));
                }
            
                /**
                * The queue used for holding tasks and handing off to worker
                * threads.  We do not require that workQueue.poll() returning
                * null necessarily means that workQueue.isEmpty(), so rely
                * solely on isEmpty to see if the queue is empty (which we must
                * do for example when deciding whether to transition from
                * SHUTDOWN to TIDYING).  This accommodates special-purpose
                * queues such as DelayQueues for which poll() is allowed to
                * return null even if it may later return non-null when delays
                * expire.
                */
                // 用于存放线程任务的阻塞队列
                private final BlockingQueue<Runnable> workQueue;
            
                /**
                * Lock held on access to workers set and related bookkeeping.
                * While we could use a concurrent set of some sort, it turns out
                * to be generally preferable to use a lock. Among the reasons is
                * that this serializes interruptIdleWorkers, which avoids
                * unnecessary interrupt storms, especially during shutdown.
                * Otherwise exiting threads would concurrently interrupt those
                * that have not yet interrupted. It also simplifies some of the
                * associated statistics bookkeeping of largestPoolSize etc. We
                * also hold mainLock on shutdown and shutdownNow, for the sake of
                * ensuring workers set is stable while separately checking
                * permission to interrupt and actually interrupting.
                */
                // 重入锁
                private final ReentrantLock mainLock = new ReentrantLock();
            
                /**
                * Set containing all worker threads in pool. Accessed only when
                * holding mainLock.
                */
                // 线程池当中的线程集合,只有当拥有mainLock锁的时候,才可以进行访问
                private final HashSet<Worker> workers = new HashSet<Worker>();
            
                /**
                * Wait condition to support awaitTermination
                */
                // 等待条件支持终止
                private final Condition termination = mainLock.newCondition();
            
                /**
                * Tracks largest attained pool size. Accessed only under
                * mainLock.
                */
                private int largestPoolSize;
            
                /**
                * Counter for completed tasks. Updated only on termination of
                * worker threads. Accessed only under mainLock.
                */
                private long completedTaskCount;
            
                /*
                * All user control parameters are declared as volatiles so that
                * ongoing actions are based on freshest values, but without need
                * for locking, since no internal invariants depend on them
                * changing synchronously with respect to other actions.
                */
            
                /**
                * Factory for new threads. All threads are created using this
                * factory (via method addWorker).  All callers must be prepared
                * for addWorker to fail, which may reflect a system or user's
                * policy limiting the number of threads.  Even though it is not
                * treated as an error, failure to create threads may result in
                * new tasks being rejected or existing ones remaining stuck in
                * the queue.
                *
                * We go further and preserve pool invariants even in the face of
                * errors such as OutOfMemoryError, that might be thrown while
                * trying to create threads.  Such errors are rather common due to
                * the need to allocate a native stack in Thread.start, and users
                * will want to perform clean pool shutdown to clean up.  There
                * will likely be enough memory available for the cleanup code to
                * complete without encountering yet another OutOfMemoryError.
                */
                // 创建新线程的线程工厂
                private volatile ThreadFactory threadFactory;
            
                /**
                * Handler called when saturated or shutdown in execute.
                */
                // 饱和策略
                private volatile RejectedExecutionHandler handler;
    
        }
    
    
    
  • 核心参数

    ctl是主要的控制状态

    1. workerCount:表示有效的线程数目

    2. runState:表示线程池里线程的运行状态

  • 重要方法

    1. execute方法
    
        // 调用execute方法将线程提交到线程池中 
        public void execute(Runnable command) {
                // 如果执行的任务为空,则会抛出空指针异常
                if (command == null)
                    throw new NullPointerException();
                /*
                * Proceed in 3 steps:
                *
                * 1. If fewer than corePoolSize threads are running, try to
                * start a new thread with the given command as its first
                * task.  The call to addWorker atomically checks runState and
                * workerCount, and so prevents false alarms that would add
                * threads when it shouldn't, by returning false.
                *
                * 2. If a task can be successfully queued, then we still need
                * to double-check whether we should have added a thread
                * (because existing ones died since last checking) or that
                * the pool shut down since entry into this method. So we
                * recheck state and if necessary roll back the enqueuing if
                * stopped, or start a new thread if there are none.
                *
                * 3. If we cannot queue task, then we try to add a new
                * thread.  If it fails, we know we are shut down or saturated
                * and so reject the task.
                */
                // 获取线程池的控制状态
                int c = ctl.get();
                // 如果workerCount值小于corePoolSize
                if (workerCountOf(c) < corePoolSize) {
                    // 添加任务到worker集合当中,成功的话返回
                    if (addWorker(command, true))
                        return;
                    // 如果失败,再次获取线程池的控制状态
                    c = ctl.get();
                }
                // 如果corePoolSize已经满了,则需要加入到阻塞队列
                // 判断线程池的状态以及是否可以往阻塞队列中继续添加runnable
                if (isRunning(c) && workQueue.offer(command)) {
                    // 获取线程池的状态
                    int recheck = ctl.get();
                    // 再次检查状态,线程池不处于RUNNING状态,将任务从workQueue队列中移除
                    if (! isRunning(recheck) && remove(command))
                        reject(command);
                    else if (workerCountOf(recheck) == 0)
                        addWorker(null, false);
                }
                // 如果此时队列已满,则会采取相应的拒绝策略
                else if (!addWorker(command, false))
                    reject(command);
            }
    
    
    
    
    

    总结

    (1) 往corePoolSize中加入任务进行执行

    (2) 当corePoolSize满时,往阻塞队列中加入任务

    (3) 阻塞队列满时并且maximumPoolSize已满,则采取相应的拒绝策略

    1. addWorker()方法
    
        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 (;;) {
                    // 获取workerCount
                    int wc = workerCountOf(c);
                    if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                        return false;
                    // 跳出循环    
                    if (compareAndIncrementWorkerCount(c))
                        break retry;
                    c = ctl.get();  // Re-read ctl
                    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 {
                // 实例化worker对象
                w = new Worker(firstTask);
                // 获取worker的线程
                final Thread t = w.thread;
                // 如果获取的线程不为空
                if (t != null) {
                    // 初始线程池的锁
                    final ReentrantLock mainLock = this.mainLock;
                    // 得到锁
                    mainLock.lock();
                    try {
                        // Recheck while holding lock.
                        // Back out on ThreadFactory failure or if
                        // shut down before lock acquired.
                        // 获取锁后再次检查,获取线程池runState
                        int rs = runStateOf(ctl.get());
                        
                        // 判断
                        if (rs < SHUTDOWN ||
                            (rs == SHUTDOWN && firstTask == null)) {
                            // 检查线程是否启动
                            if (t.isAlive()) // precheck that t is startable
                                // 如果未启动存活,则抛出异常
                                throw new IllegalThreadStateException();
                            // 往corePoolSize中加入任务    
                            workers.add(w);
                            // 获取workers集合的大小
                            int s = workers.size();
                            // 如果大小超过largestPoolSize
                            if (s > largestPoolSize)
                                // 重新设置线程池拥有最大线程数的大小
                                largestPoolSize = s;
                            // 改变状态    
                            workerAdded = true;
                        }
                    } finally {
                    // 释放锁
                        mainLock.unlock();
                    }
                    if (workerAdded) {
                        // 运行
                        t.start();
                        // 改变状态
                        workerStarted = true;
                    }
                }
            } finally {
                // 如果worker没有启动成功
                if (! workerStarted)
                    addWorkerFailed(w);
            }
            // 返回worker是否成功启动的标记
            return workerStarted;
        }
    
    
    
    
    

参考链接

https://blog.csdn.net/zycxnanwang/article/details/105563351
https://blog.csdn.net/weixin_43570367/article/details/105077014    
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值