Java线程池ThreadPoolExecutor及常见的4种线程池

文章中有参考整理其他一些有价值的博客以及官方文档的内容,如有侵权请联系删除。

我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存,因为正常来说线程执行完毕后死亡,线程对象变成垃圾!那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务呢?我们使用线程池就能很好地解决这个问题。

Executor的框架图

在这里插入图片描述
Executor接口有一个子接口ExecutorService,ExecutorService的实现类为AbstracExecutorService,而ThreadPoolExcutor正是AbstrcExecutorService的子类。ThreadPoolExecutor是线程池的核心类,此类的构造方法如下:


	// 五个参数的构造函数
 	public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
 		TimeUnit unit, BlockingQueue<Runnable> workQueue) ;


	// 六个参数的构造函数-1
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
    	TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) ;
    	
    // 六个参数的构造函数-2
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
    	TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) ;

   // 七个参数的构造函数
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
    	TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);

构造方法的参数及意义:

corePoolSize : 线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。
当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话:corePoolSize 表示允许线程池中允许同时运行的最大线程数。

maximumPoolSize : 线程池能创建最大的线程数量。如果核心线程池和缓存队列都已经满了,新的任务进来就会创建新的线程来执行。但是数量不能超过maximunPoolSize,否侧会采取拒绝接受任务策略。

keepAliveTime:表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize 时,keepAliveTime 才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown。

unit :时间单位,和keepAliveTime配合使用。

TimeUnit是一个枚举类型,其包括:
	
	NANOSECONDS : 1微毫秒 = 1微秒 / 1000
	MICROSECONDS : 1微秒 = 1毫秒 / 1000
	MILLISECONDS : 1毫秒 = 1/1000
	SECONDS : 秒
	MINUTES : 分
	HOURS : 小时
	DAYS : 天

workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。

常用的workQueue类型:
有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小。
无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE。
直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

threadFactory :线程工厂,用来创建线程,这是一个接口,new的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。

handler:拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种策略为

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不做任何处理。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不
会真的丢弃任务,但任务提交线程的性能极有可能会急剧下降。

线程池实现原理:

在这里插入图片描述
首先来看最核心的execute方法,这个方法在AbstractExecutorService中并没有实现,从Executor接口,直到ThreadPoolExecutor才实现了该方法,

ExecutorService中的submit(),invokeAll(),invokeAny()都是调用的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.
         * 如果正在运行的线程数小于corePoolSize,那么将调用addWorker 方法来创建一个新的线程,并将该任务作为新线程的第一个任务来执行。
       当然,在创建线程之前会做原子性质的检查,如果条件不允许,则不创建线程来执行任务,并返回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.
       如果任务无法入队列(队列满了),那么我们将尝试新开启一个线程(从corepoolsize到扩充到maximum),如果失败了,那么可以确定原因,     
       要么是线程池关闭了或者饱和了(达到maximum),所以我们执行拒绝策略。
         */
    
    // 1.当前线程数量小于corePoolSize,则创建并启动线程。
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
        // 成功,则返回
			return;
            c = ctl.get();
        }
    // 2.步骤1失败,则尝试进入阻塞队列,
        if (isRunning(c) && workQueue.offer(command)) {
       // 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
       // 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
    // 3. 步骤1和2失败,则尝试将线程池的数量有corePoolSize扩充至maxPoolSize,如果失败,则拒绝任务
        else if (!addWorker(command, false))
            reject(command);
    }

相信看了源码也是一脸蒙蔽,下面用更直观的流程图来讲解,它究竟干了什么事。
在这里插入图片描述
结合上面的流程图来逐行解析,首先前面进行空指针检查,

wonrkerCountOf()方法能够取得当前线程池中的线程的总数,取得当前线程数与核心池大小比较:

1.如果小于,将通过addWorker()方法调度执行。
2.如果大于核心池大小,那么就提交到等待队列。
3.如果进入等待队列失败,则会将任务直接提交给线程池。
4.如果线程数达到最大线程数,那么就提交失败,执行拒绝策略。

execute()方法中添加任务的方式是使用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;
       // 内层的循环,任务是将worker数量加1
            for (;;) {
                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
            }
        }
    // worker加1后,接下来将woker添加到HashSet<Worker>中,并启动worker
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            final ReentrantLock mainLock = this.mainLock;
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();
                    int rs = runStateOf(c);

                    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();
                }
         // 如果往HashSet<Worker>添加成功,则启动该线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

addWorker(Runnable firstTask, boolean core)的主要任务是创建并启动线程。它会根据当前线程的状态和给定的值(core or maximum)来判断是否可以创建一个线程。

线程池的任务处理策略:

如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务。

如果当前线程池中的线程数目大于等于corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理.

如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

线程池的关闭:

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

常见的四种线程池:

除了直接使用ThreadPoolExecutor构造线程池,也可以使用Executors类中提供的几个静态方法来创建线程池:

1.Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

2.Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

3.Executors.newFixedThreadPool(int var0); //创建固定容量大小的缓冲池
该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue,迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。

4.Executors.newScheduledThreadPool(int var0); //创建一个大小无限的线程池
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

其实四种线程池都是通过 ThreadPoolExecutor 来实现的,只是创建参数不同。

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值