Android笔记(十四) JAVA线程池

本文详细介绍了Java线程池的工作原理及其核心参数的作用,包括核心线程数、最大线程数、空闲线程存活时间等。同时,还介绍了线程池的四种常见类型:固定数量线程池、单线程池、缓存线程池和定时线程池。

线程池的由来:由于业务需求,我们需要多个线程同时执行(也叫并发执行),我们会去创建多个线程来执行任务。如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。因此线程池就诞生了,线程池中的线程会在任务执行完毕后不会被立即销毁,而是被重复利用,避免创建新的线程。在java中,有专门一个类用来创建线程池并使用线程池。

ThreadPoolExecutor.java

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

其中的几个重要参数的含义如下:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:the time unit for the keepAliveTime args
  • workQueue:the queue to use for holding tasks before they are excuted and only hold the Runnable tasks submited by execute() method
  • threadFactory:线程工厂,主要用来创建线程;
  • handler: the handler to use when execution is blocked because the thread bounds and queue capacities are reached

ThreadPoolExecutor类中的几个重要的方法:

  1. execute(Runnable task)。我们经常调用的submit()方法来开始任务,其内部实现也还是execute()方法。从注释可以看出,该方法里面大概走了三部
 /*
         * 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.*/
         
         if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

如果此时正在运行的线程数量少于核心线程数量,我们将开启一个新的线程,并将runnable参数作为这个线程的第一个要执行的任务。addWorker中将自动检查是否应该添加新线程。如果添加成功,将直接返回,否则重新获取此时的线程运行状态。

		 * 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.*/
         
         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);
        }

一旦新线程成功添加到workQueue,将会进行二次检测,检测新添线程运行状态。因为有可能新的线程由于种种原因出现错误状态。

		* 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.*/
         else if (!addWorker(command, false))
            reject(command);

如果我们添加线程失败,线程没有添加到workQueue中,我们将尝试新创建一个线程,如果还是创建失败,我们将会会reject(command),也就是线程池处于shutdown,我们也会reject任务

一定要弄清楚下面两个概念
//任务队列
private final BlockingQueue workQueue;
//作业线程集合
private final HashSet workers = new HashSet();
在创建ThreadPoolExecutor(ExecutorService)线程池时,我们一般可以用工厂方法来创建:

Executors.newFixedThreadPool(int workQueue size not poolsize/threadnum );

我们可以认为:workqueue为任务队列大小,workers大小和poolsize一样大。默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。线程池大小大于workers数量时,不需要添加到workqueue中;一但超过corePoolSize,则添加到queue中。如果添加到queue中失败,就会再创建新的thread,但是要确保thread数量小于maxCorePoolSize。值得注意的是,添加任务时,创建线程则要start()任务,添加到queue中时不需要主动执行任务。这是因为在worker工作的时候会不断去读取queue中的任务。
而之所以线程执行完后不会退出的原因是在Worker的run()方法中会去不断地getTask(),这个方法是阻塞的

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            if (state == SHUTDOWN)  // Help drain queue
                r = workQueue.poll();
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
                //则通过poll取任务,若等待一定的时间取不到任务,则返回null
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);//阻塞
            else
                r = workQueue.take();
            if (r != null)
                return r;
            if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出
                if (runState >= SHUTDOWN) // Wake up others
                    interruptIdleWorkers();   //中断处于空闲状态的worker
                return null;
            }
            // Else retry
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

如果取到了task就去执行runTask()

 private void runTask(Runnable task) {
        final ReentrantLock runLock = this.runLock;
        runLock.lock();
        try {
            if (runState < STOP &&
                Thread.interrupted() &&
                runState >= STOP)
            boolean ran = false;
            beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
            //自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等           
            try {
                task.run();
                ran = true;
                afterExecute(task, null);
                ++completedTasks;
            } catch (RuntimeException ex) {
                if (!ran)
                    afterExecute(task, ex);
                throw ex;
            }
        } finally {
            runLock.unlock();
        }
    }

这样就是在runnable中执行runnable()。一直这样,知道我们让它退出,或者timeout,或者我们设置了允许线程自己退出。这只是一个Worker,其他worker也是这样的,一个worker对应一个Thread,它们同时操作mworkQueue。不需要另外的分发线程DispatchThread(Volley开源库就是在工作thread基础上增加了分发线程用来管理工作线程),自己让自己poll。妙不可言!!!

JAVA中线程池的种类

注意:下面几种线程类型都是通过Executors的方法创建出来的,方法名称对应下面的线程类型,他们的实际对象还是ThreadPoolExecutor

newFixedThreadPool

建立一个线程数量固定的线程池,规定的最大线程数量,超过这个数量之后进来的任务,会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则。

创建固定线程数量线程池, corePoolSize 和 maximumPoolSize 要一致,即核心线程数和最大线程数(核心+非核心线程)一致,Executors 默认使用的是 LinkedBlockingQueue 作为等待队列,这是一个无界队列,这也是使用它的风险所在,除非你能保证提交的任务不会无节制的增长,否则不要使用无界队列,这样有可能造成等待队列无限增加,造成 OOM。

newSingleThreadExecutor

建立一个只有一个线程的线程池,如果有超过一个任务进来,只有一个可以执行,其余的都会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则。使用 LinkedBlockingQueue 作为等待队列。

这个方法同样存在等待队列无限长的问题,容易造成 OOM,所以正确的创建方式参考上面固定数量线程池创建的方式,只是把 poolSize 设置为 1

newCachedThreadPool

缓存型线程池,在核心线程达到最大值之前,有任务进来就会创建新的核心线程,并加入核心线程池,即时有空闲的线程,也不会复用。达到最大核心线程数后,新任务进来,如果有空闲线程,则直接拿来使用,如果没有空闲线程,则新建临时线程。并且线程的允许空闲时间都很短,如果超过空闲时间没有活动,则销毁临时线程。关键点就在于它使用 SynchronousQueue 作为等待队列,它不会保留任务,新任务进来后,直接创建临时线程处理,这样一来,也就容易造成无限制的创建线程,造成 OOM

newScheduledThreadPool

计划型线程池,可以设置固定时间的延时或者定期执行任务,同样是看线程池中有没有空闲线程,如果有,直接拿来使用,如果没有,则新建线程加入池。使用的是 DelayedWorkQueue 作为等待队列,这中类型的队列会保证只有到了指定的延时时间,才会执行任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值