Java中的线程池(非常重要)

本文详细介绍了线程池的重要性,特别是ExecutorService接口和ThreadPoolExecutor的作用,探讨了线程池的五大参数及其功能,涵盖了线程池中的阻塞队列类型,以及如何使用Executors工厂方法创建便捷线程池。还分析了线程池的状态转换和停止策略。
摘要由CSDN通过智能技术生成

线程池的地位十分重要,基本上涉及到跨线程的框架都使用到了线程池,比如说OkHttp、RxJava、LiveData以及协程等。

一、与新建一个线程相比,线程池的特点?

   节省开销: 线程池中的线程可以重复利用

   速度快:任务来了就能开始,省去创建线程的时间

   线程可控:线程数量可空和任务可控

   功能强大:可以定时和重复执行任务

二、ExecutorService简介

    通常来说我们说到线程池第一时间想到的就是它:ExecutorService,它是一个接口,其实如果要从真正意义上来说,它可以叫做线程池的服务,因为它提供了众多接口api来控制线程池中的线程,而真正意义上的线程池就是:ThreadPoolExecutor,它实现了ExecutorService接口,并封装了一系列的api使得它具有线程池的特性,其中包括工作队列、核心线程数、最大线程数等。

三、线程池中的几个参数

参数解释如下(重要):

corePoolSize:核心线程数量,不会释放。

maximumPoolSize:允许使用的最大线程池数量,非核心线程数量,闲置时会释放。

keepAliveTime:闲置线程允许的最大闲置时间。它起作用必须在一个前提下,就是当线程池中的线程数量超过了corePoolSize时,它表示多余的空闲线程的存活时间,即多余的空闲线程在超过keepAliveTime时间内没有任务的话则被销毁。而这个主要应用在缓存线程池中

unit:闲置时间的单位。

workQueue:阻塞队列,用来存储已经提交但未被执行的任务,不同的阻塞队列有不同的特性。

threadFactory:线程工厂,用来创建线程池中的线程,通常用默认的即可

handler:通常叫做拒绝策略,1、在线程池已经关闭的情况下 2、任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务 。在上面两种情况下,只要满足其中一种时,在使用execute()来提交新的任务时将会拒绝,而默认的拒绝策略是抛一个RejectedExecutionException异常

上面的参数理解起来都比较简单,不过workQueue这个任务队列却要再次说明一下,它是一个BlockingQueue<Runnable>对象,而泛型则限定它是用来存放Runnable对象的,刚刚上面讲了,不同的线程池它的任务队列实现肯定是不一样的,所以,保证不同线程池有着不同的功能的核心就是这个workQueue的实现了,细心的会发现在刚刚的用来创建线程池的工厂方法中,针对不同的线程池传入的workQueue也不一样,五种线程池分别用的是什么BlockingQueue:

1、newFixedThreadPool()—>LinkedBlockingQueue  无界的队列

2、newSingleThreadExecutor()—>LinkedBlockingQueue  无界的队列

3、newCachedThreadPool()—>SynchronousQueue  直接提交的队列

4、newScheduledThreadPool()—>DelayedWorkQueue  等待队列

5、newSingleThreadScheduledExecutor()—>DelayedWorkQueue  等待队列

四、线程池中用到的三种阻塞队列

1. LinkedBlockingQueue:无界的队列

它的容量是 Integer.MAX_VALUE,为 231 -1 ,是一个非常大的值,可以认为是无界队列。FixedThreadPool 和 SingleThreadExecutor 线程池的线程数是固定的,所以没有办法增加特别多的线程来处理任务,这时就需要 LinkedBlockingQueue 这样一个没有容量限制的阻塞队列来存放任务。

2. SynchronousQueue:直接提交的队列

如果不希望任务在队列中等待,而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

3. DelayedWorkQueue:等待队列

它对应的线程池分别是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,这两种线程池的最大特点就是可以延迟执行任务,比如说一定时间后执行任务或是每隔一定的时间执行一次任务。

DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构(堆的应用之一就是 优先级队列)。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。

2.5 线程池的种类有哪些:五种功能不一样的线程池:

这样创建线程池的话,我们需要配置一堆东西,非常麻烦。所以,官方也不推荐使用这种方法来创建线程池,而是推荐使用Executors的工厂方法来创建线程池Executors类是官方提供的一个工厂类,它里面封装好了众多功能不一样的线程池(但底层实现还是通过ThreadPoolExecutor),从而使得我们创建线程池非常的简便,主要提供了如下五种功能不一样的线程池:

newCachedThreadPool() :返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。

newFixedThreadPool() :线程池只能存放指定数量的线程池,线程不会释放,可重复利用。

newSingleThreadExecutor() :单线程的线程池。即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按FIFO方式顺序执行任务队列中的任务。

newScheduledThreadPool() :可定时和重复执行的线程池。

newSingleThreadScheduledExecutor():同上。和上面的区别是该线程池大小为1,而上面的可以指定线程池的大小

通过Executors的工厂方法来获取

通过Executors的工厂方法来创建线程池极其简便,其实它的内部还是通过new ThreadPoolExecutor(…)的方式创建线程池的,我们看一下这些工厂方法的内部实现:

2.6 五种线程池使用举例:

1. newFixedThreadPool 创建一个固定线程数量的线程池,示例为:

创建了一个线程数为3的固定线程数量的线程池,同理该线程池支持的线程最大并发数也是3,而我模拟了10个任务让它处理,执行的情况则是首先执行前三个任务,后面7个则依次进入任务队列进行等待,执行完前三个任务后,再通过FIFO的方式从任务队列中取任务执行,直到最后任务都执行完毕。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

    for (int i = 1; i <= 10; i++) {

        final int index = i;

        fixedThreadPool.execute(new Runnable() {

             @Override

             public void run() {

                 String threadName = Thread.currentThread().getName();

                 Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");

                 try {

                        Thread.sleep(2000);

                 } catch (InterruptedException e) {

                        e.printStackTrace();

                 }

             }

         });

     }

2. newSingleThreadExecutor

创建一个只有一个线程的线程池,每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待线程处理完再依次处理任务队列中的任务,示例为:

 ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

        for (int i = 1; i <= 10; i++) {

            final int index = i;

            singleThreadPool.execute(new Runnable() {

                @Override

                public void run() {

                    String threadName = Thread.currentThread().getName();

                    Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");

                    try {

                        Thread.sleep(1000);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            });

        }

其实我们通过newSingleThreadExecutor()和newFixedThreadPool()的方法发现,创建一个singleThreadExecutorPool实际上就是创建一个核心线程数和最大线程数都为1的fixedThreadPool。

3. newCachedThreadPool

创建一个可以根据实际情况调整线程池中线程的数量的线程池,为了体现该线程池可以自动根据实现情况进行线程的重用,而不是一味的创建新的线程去处理任务,我设置了每隔1s去提交一个新任务,这个新任务执行的时间也是动态变化的,示例为

 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 1; i <= 10; i++) {

            final int index = i;

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            cachedThreadPool.execute(new Runnable() {

                @Override

                public void run() {

                    String threadName = Thread.currentThread().getName();

                    Log.v("zxy", "线程:" + threadName + ",正在执行第" + index + "个任务");

                    try {

                        long time = index  500;

                        Thread.sleep(time);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            });

        }

 4. newScheduledThreadPool

创建一个可以定时或者周期性执行任务的线程池,示例为:

5. newSingleThreadScheduledExecutor

创建一个可以定时或者周期性执行任务的线程池,该线程池的线程数为1,示例为

这个和上面的没什么太大区别,只不过是线程池内线程数量的不同,效果为:每隔2秒就会执行一次该任务

2.7 线程池的状态:

1. RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。

2. SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。

3. STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。

4.TIDYING:SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。

线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。

  1. TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。

2.8线程池的停止

关于线程池的停止,ExecutorService为我们提供了两个方法:shutdown和shutdownNow,这两个方法各有不同,可以根据实际需求方便的运用,如下:

1. shutdown() 平滑的关闭线程池。(如果还有未执行完的任务,就等待它们执行完)。

2. shutdownNow() 简单粗暴的关闭线程池。(没有执行完的任务也直接关闭)。

2.9线程池的工作流程

简单说:

- 任务来了,优先考虑核心线程。

- 核心线程满了,进入阻塞队列。

- 阻塞队列满了,考虑非核心线程。

- 非核心线程满了,再触发拒绝任务。

详细说明:

1 当一个任务通过submit或者execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,则创建一个线程执行该任务。

2 如果当前线程池中线程数已经达到coolPoolSize,则将任务放入等待队列。

3 如果任务不能入队,说明等待队列已满,若当前池中线程数小于maximumPoolSize,则创建一个临时线程(非核心线程)执行该任务。

4 如果当前池中线程数已经等于maximumPoolSize,此时无法执行该任务,根据拒绝执行策略处理。

注意:当池中线程数大于coolPoolSize,超过keepAliveTime时间的闲置线程会被回收掉。回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAliveTime时间后也会被回收。

任务队列是一个阻塞队列,线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值