线程池

作者: stoneyang94 | 来源:发表于2018-06-21 19:07 被阅读0次

    主要用于概念扫盲和梳理。
    本文主要为什么使用线程池,ThreadPoolExecutor构造函数,常见的线程池种类。

    一. 为什么用线程池

    1. 系统开销

    创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率

    例如:
    记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
    如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

    线程池缓存线程,可复用的闲置线程来执行新任务,避免了T1+T3带来的系统开销

    2. 系统资源

    线程并发数量过多,抢占系统资源从而导致阻塞

    我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况

    运用线程池能有效的控制线程最大并发数,避免以上的问题

    3. 管理线程

    对线程进行一些简单的管理

    比如:延时执行、定时循环执行的策略

    运用线程池都能进行很好的实现>

    二. 继承关系

    总览

    线程池

    常用的几个类或接口

    ExecutorService

    线程池接口,继承于Executor接口,多了Submit()shutdown()等方法

    ScheduledExecutorService

    功能和Timer类似,解决那些需要任务重复执行的问题,负责线程的调度

    ThreadPoolExecutor

    线程池的实现类

    ScheduledThreadPoolExecutor

    继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

    Executors

    一个工具类,提供几个创建线程池的方法

    Future 接口

    它和FutureTask类(实现了Future接口)都用来表示异步计算的结果。

    三. 构造线程池

    java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。

    • 在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类
    • 对线程池的配置,就是对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)
    

    参数解释

    1. int corePoolSize ---核心线程数最大值

    • 线程池新建线程的时候
      1. 如果当前线程总数小于corePoolSize,则新建的是核心线程
      2. 如果超过corePoolSize,则新建的是非核心线程
    • 核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
    • 如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

    2. int maximumPoolSize---线程总数最大值

    • 线程总数 = 核心线程数 + 非核心线程数

    3. long keepAliveTime---非核心线程闲置超时时长

    • 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
    • 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

    4. TimeUnit unit---量纲

    keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:

    单位 时长
    NANOSECONDS 1微毫秒 = 1微秒 / 1000
    MICROSECONDS 1微秒 = 1毫秒 / 1000
    MILLISECONDS 1毫秒 = 1秒 /1000
    SECONDS
    MINUTES
    HOURS 小时
    DAYS

    5. BlockingQueue<Runnable> workQueue--任务队列

    • 维护着等待执行的Runnable对象
    • 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

    常用的workQueue类型:

    SynchronousQueue

    它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

    这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现线程数达到了maximumPoolSize而不能新建线程的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

    LinkedBlockingQueue

    基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

    这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。

    由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

    ArrayBlockingQueue

    基于数组的先进先出队列,此队列创建时必须指定大小

    可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

    DelayQueue

    队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

    6. ThreadFactory threadFactory

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

    AsyncTask新建线程池的threadFactory参数源码:

    new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        
        public Thread new Thread(Runnable r) {
            return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
        }
    }
    

    7. RejectedExecutionHandler handler

    表示当拒绝处理任务时的策略,有以下四种取值:

    取值 策略
    ThreadPoolExecutor.AbortPolicy(默认) 丢弃任务并抛出RejectedExecutionException异常
    ThreadPoolExecutor.DiscardPolicy 也是丢弃任务,但是不抛出异常
    ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy 由调用线程处理该任务,线程调用运行该任务的execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度

    ThreadPoolExecutor的策略

    上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:

    1. 核心线程满了吗
      1.1 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
      1.2 线程数量达到了corePools,则将任务移入队列等待
    2. 队列满了吗
      2.1 队列已满,新建线程(非核心线程)执行任务
      2.2 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常

    总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于 corePoolSize,再看缓冲队列 workQueue 是否满,最后看线程池中的线程数量是否大于 maximumPoolSize。

    另外,当线程池中的线程数量大于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。

    ThreadPoolExecutor类中重要方法

    方法 作用
    execute() 通过这个方法可以向线程池提交一个任务,交由线程池去执行
    submit() 实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果
    shutdown() 不会立即终止线程池,而是要所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
    shutdownNow() 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

    四. Executor与Executors

    关系

    Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

    对比

    • Executor 框架
      Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架
      无限制的创建线程会引起应用程序内存溢出,所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用 Executor 框架可以非常方便的创建一个线程池。

    • Executors 类
      Executors为Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池。

    五. 常见四种线程池

    Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的

    1. CachedThreadPool()--可缓存线程池

    特点

    1. 线程数无限制
    2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程
    3. 一定程度上减少频繁创建/销毁线程,减少系统开销

    场合

    缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的 daemon 型 SERVER 中用得不多。
    但对于生存期短的异步任务,它是 Executor 的首选。

    源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    2. FixedThreadPool(int nThreads)--定长线程池

    特点

    1. 可控制线程最大并发数(同时执行的线程数)
    2. 超出的线程会在队列中等待

    场合

    FixedThreadPool 多数针对一些很稳定很固定的正规并发线程,多用于服务器。

    //threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看!
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
    源码:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    

    2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮

    3. ScheduledThreadPool(int corePoolSize)--定核心线程池

    1. 支持定时及周期性任务执行。

    创建方法:

    //nThreads => 最大线程数即maximumPoolSize
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
    源码:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor():
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    

    4. SingleThreadExecutor()--单线程化的线程池

    1. 有且仅有一个工作线程执行任务
    2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则

    创建方法:

    ExecutorService singleThreadPool = Executors.newSingleThreadPool();

    源码:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    

    六. 实际例子

    Executor 执行 Runnable 任务

    通过 Executors 的以上四个静态工厂方法获得 ExecutorService 实例,而后调用该实例的 execute(Runnable command)方法即可。一旦 Runnable 任务传递到 execute()方法,该方法便会自动在一个线程上执行。

    import java.util.concurrent.ExecutorService;   
    import java.util.concurrent.Executors;   
    
    public class TestCachedThreadPool{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
    //      ExecutorService executorService = Executors.newFixedThreadPool(5);  
    //      ExecutorService executorService = Executors.newSingleThreadExecutor();  
            for (int i = 0; i < 5; i++){   
                executorService.execute(new TestRunnable());   
                System.out.println("************* a" + i + " *************");   
            }   
            executorService.shutdown();   
        }   
    }   
    
    class TestRunnable implements Runnable{   
        public void run(){   
            System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
        }   
    }
    

    输出:(每次可能不一样)

    ************* a0 *************
    pool-1-thread-1线程被调用了。
    ************* a1 *************
    pool-1-thread-2线程被调用了。
    ************* a2 *************
    pool-1-thread-3线程被调用了。
    ************* a3 *************
    ************* a4 *************
    pool-1-thread-1线程被调用了。
    pool-1-thread-3线程被调用了。
    

    Executor 执行 Callable 任务

    在 Java 5 之后,任务分两类:一类是实现了 Runnable 接口的类,一类是实现了 Callable 接口的类。两者都可以被 ExecutorService 执行,但是 Runnable 任务没有返回值,而 Callable 任务有返回值。并且 Callable 的 call()方法只能通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。

    import java.util.ArrayList;   
    import java.util.List;   
    import java.util.concurrent.*;   
    
    public class CallableDemo{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
            List<Future<String>> resultList = new ArrayList<Future<String>>();  
            for (int i = 0; i < 10; i++){   //创建10个任务并执行  
                //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
                Future<String> future = executorService.submit(new TaskWithResult(i));   
                //将任务执行结果存储到List中   
                resultList.add(future);   
            }   
    
            //遍历任务的结果   
            for (Future<String> fs : resultList){   
                    try{   
                        while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                        System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                    }catch(InterruptedException e){   
                        e.printStackTrace();   
                    }catch(ExecutionException e){   
                        e.printStackTrace();   
                    }finally{
                        executorService.shutdown();   
                    }   
            }   
        }   
    }   
    
    class TaskWithResult implements Callable<String>{   
        private int id;   
    
        public TaskWithResult(int id){   
            this.id = id;   
        }   
    
        //任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行 
        public String call() throws Exception {  
            System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
            //该返回结果将被Future的get方法得到  
            return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
        }   
    }  
    

    输出:(每次可能不一样)

    call()方法被自动调用!!!    pool-1-thread-1
    call()方法被自动调用!!!    pool-1-thread-3
    call()方法被自动调用!!!    pool-1-thread-1
    call()方法被自动调用!!!    pool-1-thread-2
    call()方法被自动调用!!!    pool-1-thread-8
    call()方法被自动调用!!!    pool-1-thread-5
    call()方法被自动调用!!!    pool-1-thread-9
    call()方法被自动调用!!!    pool-1-thread-4
    call()方法被自动调用!!!    pool-1-thread-6
    call()方法被自动调用!!!    pool-1-thread-7
    call()方法被自动调用,任务返回的结果是:0    pool-1-thread-1
    call()方法被自动调用,任务返回的结果是:1    pool-1-thread-2
    call()方法被自动调用,任务返回的结果是:2    pool-1-thread-3
    call()方法被自动调用,任务返回的结果是:3    pool-1-thread-4
    call()方法被自动调用,任务返回的结果是:4    pool-1-thread-5
    call()方法被自动调用,任务返回的结果是:5    pool-1-thread-6
    call()方法被自动调用,任务返回的结果是:6    pool-1-thread-7
    call()方法被自动调用,任务返回的结果是:7    pool-1-thread-8
    call()方法被自动调用,任务返回的结果是:8    pool-1-thread-1
    call()方法被自动调用,任务返回的结果是:9    pool-1-thread-9
    

    参考文章
    线程池,这一篇或许就够了
    Java并发编程:线程池的使用
    官方文档
    【Java多线程】线程池的工作原理详解(上)
    极客学院

    相关文章

      网友评论

        本文标题:线程池

        本文链接:https://www.haomeiwen.com/subject/zrhxyftx.html