ThreadPoolExecuter基本的使用与理解

线程池概念

为什么要使用到线程池呢?

  1. 节约性能,因为在使用new Thread()创建线程的时候,这个过程是比较耗时的,会消耗一定的资源。
  2. 便于管理,通过new Thread()创建的线程,被称之为野线程,可以无限的创建,而线程之间互相的竞争资源也会导致系统资源的浪费,最后当线程很多的时候,高速切换对CPU的占有权也是对性能有影响的,并且线程的生命周期有自己决定,得不到较好的维护。
  3. 便于拓展,线程池的线程可以比较简单的设置定时执行,定期执行等。
  4. 调用简单,由于线程池对线程的封装很完善,调用起来就很简单,封装越难,使用越简单。

线程池的状态

在这里插入图片描述
线程池的shutdown() 方法,将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态
线程池的shutdownNow()方法,将线程池由RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。

注:SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED

ThreadPoolExecutor构造函数

在这里插入图片描述

线程池工作原理:

corePoolSize :线程池中核心线程数的最大值
maximumPoolSize :线程池中能拥有最多线程数
workQueue:用于缓存任务的阻塞队列
当调用线程池execute() 方法添加一个任务时,线程池会做如下判断

  • 如果有空闲线程,则直接执行该任务;
  • 如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;
  • 如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;
  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;
  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler指定的策略来拒绝新的任务。

在这里插入图片描述

KeepAliveTime:

  • keepAliveTime :表示空闲线程的存活时间
  • TimeUnit unit :表示keepAliveTime的单位
  • 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于
    corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

注:如果线程池设置了allowCoreThreadTimeout参数为true(默认false),那么当空闲线程超过keepaliveTime后直接停掉。(不会判断线程数是否大于corePoolSize)即:最终线程数会变为0。
workQueue 任务队列:

workQueue :它决定了缓存任务的排队策略

ThreadPoolExecutor线程池推荐了三种等待队列,它们是:SynchronousQueue 、LinkedBlockingQueue 和 ArrayBlockingQueue。

1)有界队列

  • SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool使用了这个队列。
  • ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

2)无界队列

  • LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)
  • PriorityBlockingQueue:是一个按照优先级进行内部元素排序的无界阻塞队列。队列中的元素必须实现 Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序。

注意:keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义

threadFactory:

threadFactory :指定创建线程的工厂。(可以不指定)

  • 如果不指定线程工厂时,ThreadPoolExecutor会使用ThreadPoolExecutor.defaultThreadFactory创建线程。默认工厂创建的线程:同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。

handler 拒绝策略:

handler :表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。(可以不指定)
在这里插入图片描述

成员方法

  • getCorePoolSize():返回线程池的核心线程数,这个值是一直不变的,返回在构造函数中设置的coreSize大小;

  • getMaximumPoolSize():返回线程池的最大线程数,这个值是一直不变的,返回在构造函数中设置的coreSize大小;

  • getLargestPoolSize():记录了曾经出现的最大线程个数(水位线);

  • getPoolSize():线程池中当前线程的数量;

  • getActiveCount():Returns the approximate(近似) number of threads that
    are actively executing tasks;

  • prestartAllCoreThreads():会启动所有核心线程,无论是否有待执行的任务,线程池都会创建新的线程,直到池中线程数量达到corePoolSize;

  • prestartCoreThread():会启动一个核心线程(同上);

  • allowCoreThreadTimeOut(true):允许核心线程在KeepAliveTime时间后,退出;

Executors类:

Executors类的底层实现便是ThreadPoolExecutor! Executors 工厂方法有:

  • Executors.newCachedThreadPool():无界线程池,可以进行自动线程回收
  • Executors.newFixedThreadPool(int):固定大小线程池
  • Executors.newSingleThreadExecutor():单个后台线程

线程池相关接口

1、ExecutorService接口:

该接口是真正的线程池接口。上面的ThreadPoolExecutor以及下面的ScheduledThreadPoolExecutor都是该接口的实现类。改接口常用方法:

  • Future<?> submit(Runnabletask):提交Runnable任务到线程池,返回Future对象,由于Runnable没有返回值,也就是说调用Future对象get()方法返回null;
  • Future submit(Callabletask):提交Callable任务到线程池,返回Future对象,调用Future对象get()方法可以获取Callable的返回值;
  • Future submit(Runnable task,T result):提交Runnable任务到线程池,返回Future对象,调用Future对象get()方法可以获取Runnable的参数值;
  • invokeAll(collection of tasks)/invokeAll(collection of tasks, long timeout, TimeUnit unit):invokeAll会按照任务集合中的顺序将所有的Future添加到返回的集合中,该方法是一个阻塞的方法。只有当所有的任务都执行完毕时,或者调用线程被中断,又或者超出指定时限时,invokeAll方法才会返回。当invokeAll返回之后每个任务要么返回,要么取消,此时客户端可以调用get/isCancelled来判断具体是什么情况。
  • invokeAny(collection of tasks)/invokeAny(collection of tasks, long timeout, TimeUnit unit):阻塞的方法,不会返回 Future 对象,而是返回集合中某一个Callable对象的结果,而且无法保证调用之后返回的结果是哪一个 Callable,如果一个任务运行完毕或者抛出异常,方法会取消其它的 Callable的执行。和invokeAll区别是只要有一个任务执行完了,就把结果返回,并取消其他未执行完的任务;同样,也带有超时功能;
  • shutdown():在完成已提交的任务后关闭服务,不再接受新任;
  • shutdownNow():停止所有正在执行的任务并关闭服务;
  • isTerminated():测试是否所有任务都执行完毕了;
  • isShutdown():测试是否该ExecutorService已被关闭。

Submit()使用实例

package ThreadLearning;

import java.util.concurrent.*;

/**
 * 线程池使用demo
 * 测试案例,使用ThreadPoolExecutor类来构造线程池对象,注入Callable任务或者是Runnable任务来测试submit()方法
 * 以及其他的相关小方法。
 */
public class ExecutorServiceDemo {

    public static void main(String[] args) {
        //构造线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2, 5, 1000L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "handle-callable-"+r.hashCode());
                    }
                },new ThreadPoolExecutor.AbortPolicy()
        );
        //加入任务
        Future<String> callableTaskRes = threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"开始执行");
                    Thread.sleep(1000L);
                    System.out.println("在执行睡觉任务....");
                    System.out.println("执行完毕");
                }
                return "end,i am ok";
            }
        });
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"Runnable");
            }
        });
        try {
            System.out.println(callableTaskRes.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (threadPoolExecutor.isTerminated()) {
            threadPoolExecutor.shutdown();
        }

    }

}

submit()和execute()方法区别:

ExecutorService、ScheduledExecutorService接口的submit()和execute()方法都是把任务提交到线程池中,但二者的区别是:

  • 接收的参数不一样,execute只能接收Runnable类型、submit可以接收Runnable和Callable两种类型;
  • submit有返回值,而execute没有返回值;submit方便Exception处理;

是因为Future需要返回结果,所以内部task必须是Callable,如果task是Runnable 就偷天换日,在Runnable 外面包个Callable马甲,返回的结果在构造时就写好。关于参数获取的原理可查看文章


版权声明:本文为qq_55946115原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。