线程池

池化技术的好处

  1. 降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

线程池的应用场景

  1. 服务器接受到大量请求时,使用线程池技术时非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
  2. 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

线程池的类关系图

image-20200621135143739

线程池的构造器参数

参数名类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTimelong保持存活时间
workQueueBlockingQueue任务存储队列
threadFactoryThreadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
HandlerRejectedExecutionHandler由于线程池无法接受所提交的任务的拒绝策略

corePoolSize和maxPoolSize

  • corePoolSize指的是核心线程数:线程池在完成初始化后,默认情况下,还没有创建任何线程,线程池会等待有任务到来时,再创建新线程去执行任务,直到达到核心线程数,之后核心线程会一直保持这个数量;当任务数量超过核心线程数,将任务放在阻塞队列workQueue中,等待核心线程空闲后处理
  • 如果核心线程全部在工作中,而且队列也满了,线程池就会在核心线程的基础上,额外增加一些线程,这些新增加的线程数最大上限就是maxPoolSize

image-20200621142929332

线程创建规则

  1. 如果线程数小于corePoolSize, 即使其他线程处于空闲状态,也会创建一个新线程(核心线程)来运行新任务
  2. 如果线程数等于(或大于)corePoolSize但少于maxPoolSize,则将任务放入队列
  3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
  4. 如果队列已满,并且线程数大于或等于maxPoolSize则拒绝该任务

thread_pool

增减线程的特点

  1. 通过设置corePoolSize和maxPoolSize为相同数量,就可以创建固定大小的线程池,即使队列满了也不会在拓展线程
  2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它,这就是队列的用意
  3. 通过设置maxPoolSize为很高的只,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
  4. 是只有在队列填满时才创建多于corePoolSize的线程,所以如果使用无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize

keepAliveTime

空闲的非核心线程的存活时间,用于回收线程

  • 如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止

ThreadFactory

线程工厂,用于创建线程

  • 新的线程是由ThreadFactory创建的,默认使用的线程工厂是Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY优先级并且都不是守护线程;如果自己定义ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等
  • 通常使用默认的就可以,源码如下:

image-20200621150205849

workQueue

有三种最常见的队列类型:

  1. 直接交接: SynchronousQueue 无容量
  2. 无界队列: LinkedBlockingQueue 无限容量,有内存溢出的风险
  3. 有界队列: ArrayBlockingQueue 可设置容量

ThreadPoolExecutor的启动

/**
*  -通过 new 创建线程池时, 除非调用 prestartAllCoreThreads / prestartCoreThread 方法启动核心线程,
*  -否则即使工作队列中存在任务,同样也不会执行.
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 20, 3L, TimeUnit.SECONDS, linkedBlockingDeque);
        
/**
 * Starts all core threads, causing them to idly wait for work. This
* overrides the default policy of starting core threads only when
* new tasks are executed.
*    -启动所有核心线程,让它们无空闲的等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。
* -手动启动线程池.
* @return the number of threads started
*/
threadPoolExecutor.prestartAllCoreThreads();

JDK内置线程池

线程池应该手动创建还是自动创建

手动创建,可以让我们更加明确线程池的允许规则,避免资源耗尽的风险

自动创建,也就是直接调用JDK封装号的构造函数,可能会带来一些问题:

Executors.newFixedThreadPool(int nThreads)数量固定的线程池

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

corePoolSize和maxPoolSize被设置为相同的nThreads参数,并使用了无界队列LinkedBlockingQueue,不会拓展线程所以也没有存活时间

当任务在队列中堆积过多,可能就会造成OOM

Executors.newSingleThreadExecutor()只有一个线程的线程池

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

Executors.newCachedThreadPool() 可缓存线程

无界线程池,具有自动回收多余线程的功能

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

最大的线程数被设置为Integer.MAX_VALUE,线程空闲60秒后回收,不使用队列(SynchronousQueue)

Executors.newScheduledTreadPool()

支持定时及周期性任务执行的线程池,使用延迟队列(DelayedWorkQueue)

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

        //延迟5秒执行任务
        threadPool.schedule(new EveryTaskOneThread.Task(),5, TimeUnit.SECONDS);
        //1秒之后每个3秒执行一次任务
        threadPool.scheduleAtFixedRate(new EveryTaskOneThread.Task(),
                1, 3,TimeUnit.SECONDS);
    }

所以,还是更具业务的并发量手动创建线程池吧

JDK1.8后加入workStealingPool

  • 子任务
  • 窃取

线程数量怎么设定?

  • CPU密集型(加密,即使hash等) : 最佳线程数为CPU核心数的1-2倍左右
  • 耗时I/O型(读写数据库,文件,网络传输等): 最佳线程数一般会大于CPU核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考Brain Goetz推荐的计算方法:

    ==线程数=CPU核心数 * (1+平均等待时间/平均工作时间))==

  • 实际上最靠谱的还是通过压力测试得出合适的线程数

停止线程池的正确方式

  • shutdown 执行该方法后,线程池会将当前队列中的任务执行完毕,并且在次期间拒绝新任务进入,执行完后停止线程池
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++){
            executorService.execute(new ShutDownTask());
        }
        System.out.println(executorService.isShutdown());
        Thread.sleep(1500);
        executorService.shutdown();
        //是否进入停止状态
        System.out.println(executorService.isShutdown());
        //拒绝新任务
        executorService.execute(new ShutDownTask());
        //是否真正意义上的关闭
        System.out.println(executorService.isTerminated());
    }

    static class ShutDownTask implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

awaitTermination(timeout):在一段时间内所有任务是否被执行完毕

  • shutdownNow 将所有线程中断,并且队列中还未执行的任务作为一个列表返回
public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++){
            executorService.execute(new ShutDownTask());
        }
        System.out.println(executorService.isShutdown());
        Thread.sleep(1500);
        //发送中断信号,并返回runnableList
        List<Runnable> runnableList =
                executorService.shutdownNow();
    }

    static class ShutDownTask implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被中断了");
            }
        }
    }

任务拒绝策略

  • 拒绝时机

    1. 当Executor关闭(shutdown)时,提交新任务会被拒绝
    2. 当Executor对最大线程和队列容量使用有限制并且已经饱和时

4种拒绝策略

  • AbortPolicy: 默认,直接抛出RejectedExecutionException拒绝异常
  • DiscardPolicy: 默默的把被拒绝的任务丢弃
  • DiscardOldestPolicy: 当有新任务时,会丢弃任务队列中存在最久的老任务,以腾出位置给新任务
  • CallerRunsPolicy: 将被线程池拒绝的任务交给调用者(caller)主线程去执行

钩子方法

每个任务执行前后可以增加处理(日志,统计)

/**
 * 演示每个任务执行前后都可以放钩子函数
 */
public class PauseableTreadPool extends ThreadPoolExecutor {

    private final ReentrantLock lock = new ReentrantLock();
    private Condition unpaused = lock.newCondition();

    //标记线程是否处于暂停状态
    private boolean isPaused;

    public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableTreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    //重写方法 before钩子
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while(isPaused) {
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    //暂停方法
    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }
    //恢复方法
    private void resume() {
        lock.lock();
        try{
            isPaused = false;
            //唤醒全部
            unpaused.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PauseableTreadPool pauseableTreadPool =
                new PauseableTreadPool(10, 20,
                10l, TimeUnit.SECONDS, new LinkedBlockingDeque<>());

        Runnable runnable = new Runnable() { //线程体
            @Override
            public void run() {
                System.out.println("被执行");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10000; i++){
            pauseableTreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableTreadPool.pause();
        System.out.println("线程池被暂停");
        Thread.sleep(1500);
        pauseableTreadPool.resume();
        System.out.println("线程池已恢复");
    }
}

原理&源码分析

主要分析ThreadPoolExecutor

线程池的组成部分

  • 线程池管理器 ExecutorService控制线程池的启动和停止
  • 工作线程 ThreadPoolExecutor中的内部类Worker
  • 任务队列 线程安全的BlockingQueue<Runnable> workQueue;
  • 任务接口(Task)

    /**
     * Creates with given first task and thread from ThreadFactory.

*/
Worker(Runnable firstTask) {

  setState(-1); // inhibit interrupts until runWorker
  this.firstTask = firstTask;
  this.thread = getThreadFactory().newThread(this);

}


**线程池事项任务复用的原理**

- 用相同的线程执行不同的任务

**ThreadPoolExecutor中的execute方法**
/**
 * 在将来的某个时间执行给定的任务,任务可以在新线程或池中现有的线程中执行
 * 如果无法将任务提交执行,原因之一是执行器已关闭或由于其容量已满,该任务由当前的 RejectedExecutionHandler 处理
 * @param command 要执行的任务
 * 
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. 如果工作线程数量少于corePoolSize,尝试调用addWorker以给定的command启动一个新线程
     *
     * 2.如果一个任务可以成功排队,那么我们仍然需要
     * 仔细检查我们是否应该添加线程
     * (因为现有的自上次检查后死亡)或
     * 自从进入此方法以来,该池已关闭。 所以我们
     * 重新检查状态,并在必要时回退排队
     * 停止,如果没有,则启动一个新线程。
     *
     * 3.如果我们无法将任务排队,那么我们尝试添加一个新的
     * 线程。 如果失败,我们知道我们已经关闭或饱和
     * 并因此拒绝任务。
     */
    int c = ctl.get(); //ctl记录了线程池状态和线程数
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

final void runWorker(Worker w) {

    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try { //循环获取任务执行
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

## 线程池的状态

- RUNNING: 接受型任务并处理排队任务
- SHUTDOWN: 不接受任务,但处理排队任务
- STOP: 不接受新任务,也不处理排队任务,并中断正在进行的任务
- TIDYING(整洁): 所有任务都已终止, workerCount为0时,线程会转换到TIDYING状态, 并将运行 terminate() 钩子方法
- TERMINATED: terminate() 运行完成

// 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;

## 使用线程池的注意点

- 避免任务的堆积

   FixedThreadPool  SingleThreadExecutor
   任务队列长度过大, 可能会堆积大量的请求, 从而导致OOM.

- 避免线程数过度增加

  CachedThreadPool ScheduledThreadPool
  允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 

- 排查线程泄漏

  线程已经执行完毕,却没有正确的被回收,往往是任务的逻辑问题

打了个冷颤
19 声望0 粉丝

且听风吟