深入理解并发编程之线程池ThreadPoolExecutor源码分析

深入理解并发编程之线程池ThreadPoolExecutor源码分析


一、线程池简介

1.为什么要使用线程池

因为频繁的开启线程或者停止,线程需要从新被cpu从就绪到运行状态调度,效率非常低。所以使用线程可以实现复用,从而提高效率。

2.线程池的作用

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行

3.线程池的创建方式

线程池有四种创建方式,这些线程池底层都是基于ThreadPoolExecutor构造函数封装的。
Executors.newCachedThreadPool(); 可缓存线程池
Executors.newFixedThreadPool();可定长度
Executors.newScheduledThreadPool() ; 可定时
Executors.newSingleThreadExecutor(); 单例
下面为线程池的使用代码:

public class Test01 {
    public static void main(String[] args) {
        // 底层中只会创建两个线程复用
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            executorService.execute(
                    new Runnable() {
                        @Override
                        public void run() {
                            //打印正在执行的缓存线程信息
                            System.out.println(Thread.currentThread().getName() + "正在被执行");
                        }
                    }
            );
        }
    }
}

看一下执行的结果,线程池里面创建了两个线程,其实一直是这两个线程在复用,并不是创建了10个线程,这样减少了创建线程和销毁线程的消耗:
在这里插入图片描述

4.阻塞队列

我们点击进入newFixedThreadPool()方法发现线程池的实现是基于阻塞队列的,下面来介绍几种常用的阻塞队列:

  1. ArrayBlockingQueue: 有界队列,基于数组结构,按照队列FIFO原则对元素排序;
  2. LinkedBlockingQueue: 无界队列,基于链表结构,按照队列FIFO原则对元素排序,Executors.newFixedThreadPool()使用了这个队列; 默认是Integer.MAX_VALUE,有界则是可以自己定义.
  3. SynchronousQueue: 同步队列,该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直被阻塞,Executors.newCachedThreadPool()使用了这个队列;
  4. PriorityBlockingQueue: 优先级队列,具有优先级的无限阻塞队列。

二、线程池原理分析

1.ThreadPoolExecutor代码分析

先看一下ThreadPoolExecutor的构造方法:

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
    						 ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?null : AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面分别说一下这些参数的具体含义:

  • corePoolSize: 核心线程数量,也就是一直正在保持运行的线程。
  • maximumPoolSize: 最大线程数,线程池允许创建的最大线程数。
  • keepAliveTime: 超出corePoolSize后创建的线程的存活时间。
  • unit: keepAliveTime的时间单位
  • workQueue: 任务队列,用于保存待执行的任务。
  • threadFactory: 线程池内部创建线程所用的工厂。
  • handler: 任务无法执行时的处理器。

线程池有五种状态:

    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;
  1. RUNNING: 线程池能够接受新任务,以及对新添加的任务进行处理。
  2. SHUTDOWN: 线程池不可以接受新任务,但是可以对已添加的任务进行处理。
  3. STOP: 线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
  4. TIDYING: 当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行构造函数。
  5. TERMINATED: 线程池彻底终止的状态。

2.ThreadPoolExecutor的逻辑

线程池接收线程任务调度的逻辑是这样的:

  1. 提交任务的时候比较核心线程数,如果当前任务数量小于核心线程数的情况下,则直接复用线程执行。
  2. 如果任务量大于核心线程数,则缓存到队列中。
  3. 如果缓存队列满了,且任务数小于最大线程数的情况下,则创建线程执行;如果队列且最大线程数都满的情况下,则走拒绝策略
    在这里插入图片描述
    我们看一下源码:
 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果当前任务数程数小于核心线程数
        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);
            //如线程池已关闭,且工作线程为0,则创建一个空闲工作线程  
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //这里传的是false,则addWorker判断的是最大线程数,如果超出最大线程数就走拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }
	//增加核心线程操作
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get(); //读取当前任务数量
            int rs = runStateOf(c);
            //如果线程池不是停止状态 && !(线程池停止状态&&传入的线程为空&&队列不为空)直接返回false
            if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
                return false;
            for (;;) {
            	//获取任务数量
                int wc = workerCountOf(c);
                //如果任务数量大于最大容量||或者任务数量大于核心线程数量 直接返回false
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //CAS任务数量+1
                if (compareAndIncrementWorkerCount(c)) 
                    break retry;
                c = ctl.get();  //重新读取当前任务数量
                if (runStateOf(c) != rs)  //工作状态判断
                    continue retry;
            }
        }
		//上面的都走完了,说明此时任务数量是小于核心线程数量的
        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()); //获取当前工作状态
                    //如果工作状态为运行中 || 工作关闭传入的任务为空
                    if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        //核心线程容器增加当前任务
                        workers.add(w);
                        int s = workers.size();
                        //跟踪最大核心线程池的数量,这就是最后一步队列满了而最大线程数量没满
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果核心线程增加成功,运行核心线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

再看一下线程池运行线程的方法,可以看出线程池的核心线程一直死循环运行,直到缓存线程为0或者线程池停止:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); 
        boolean completedAbruptly = true;
        try {
        	// getTask不为空,这是在任务队列里面获取一个任务,只要任务队列不为空则一直循环
            while (task != null || (task = getTask()) != null) {
                w.lock(); 
                //状态判断,如果线程池停止则通过中断退出循环停止操作
                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);
        }
    }

看完源码完整的逻辑图是这样的:
在这里插入图片描述

三、线程池常见问题

1.线程池队列拒绝策略

如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略,拒绝策略是可以自定义的。
rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:

  1. 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
  2. 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。

线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:

  1. AbortPolicy 丢弃任务,抛运行时异常
  2. CallerRunsPolicy 执行任务
  3. DiscardPolicy 忽视,什么都不会发生
  4. DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
  5. 实现RejectedExecutionHandler接口,可自定义处理器

2.线程池参数配置多少最合理

CPU密集型服务器: CPU密集,线程不会阻塞,一直在运行,线程代码非常快结束,则最大线程数配置与CPU核数相当就可以了,线程数正好为CPU内核数量是为了减少切换线程时的资源浪费。
IO密集型服务器: IO密集,比如读取IO、导致当前线程有可能阻塞,或者线程执行代码非常耗时,就可以多设置些线程,让等待的这段时间线程可以去做其他事,提高并发处理效率,则最大线程数配置为Cpu核数*2即可。

3.使用Executors的弊端

下面阿里的 Java开发手册中的一个强制:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
在这里插入图片描述
因为使用的是无解队列,无界队列默认最大长度为Integer.MAX_VALUE,这是下面阿里规范提到的问题,因为无界情况下缓存的线程是无上限的,会导致内存溢出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值