ThreadPoolExecutor线程池解析及Executor创立线程常见四种方式

作者 : 开心源码 本文共6498个字,预计阅读时间需要17分钟 发布时间: 2022-05-12 共182人阅读

前言:

在刚学Java并发的时候基本上第一个demo都会写new Thread来创立线程。但是随着学的深入之后发现基本上都是使用线程池来直接获取线程。那么为什么会有这样的情况发生呢?

new Thread和线程池的比较

每次new Thread是新建了线程对象,并且不能重复使用,为什么不能重复使用?由于new是相当于在内存中独立开拓一个内存来让该线程运行,所以只能释放线程资源和新建线程,性能差。而使用线程池,可以重复使用存在的线程,减少对象的创立,消亡的开销,性能较好

线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多的系统资源导致死机或者者抛出OutOfMemoryError。而使用线程池可以有效控制最大并发线程数,提高系统资源利用率,同时避免过多资源竞争,避免阻塞

同时new Thread,当我们需要定期执行,更多执行,线程中断等等使用Thread操作起来非常的繁琐。线程池则提供定时执行,定期执行,单线程,并发控制等功能,让我们操作线程起来特别方便

ThreadPoolExecutor如何创立对象

在这里详情的是JUC包下的ThreadPoolExecutor线程池,这个线程池里有4个构造方法

public class ThreadPoolExecutor extends AbstractExecutorService{//第一个构造方法public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             Executors.defaultThreadFactory(), defaultHandler);    }//第二个构造方法public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             threadFactory, defaultHandler);    }//第三个构造方法public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              RejectedExecutionHandler handler) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             Executors.defaultThreadFactory(), handler);    }//第四个也是真正的初始化构造函数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;    }}

从这四个函数来看,其实是分能否需要默认的线程池工厂和handler。接下来就讲讲这些参数代表什么。

corePoolSize:核心线程数量。当线程数少于corePoolSize的时候,直接创立新的线程,虽然其余线程是空闲的。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

maximunPoolSize:线程池最大线程数。假如线程数量少于线程最大数且大于核心线程数量的时候,只有当阻塞队列满了才创立新线程。当线程数量大于最大线程数且阻塞队列满了这时候就会执行少量策略来响应该线程。

workQueue:阻塞队列,存储等待执行的任务,会对线程池的运行产生很大的影响。当提交一个新的任务到线程池的时候,线程池会根据当前线程数量来选择不同的解决方式

直接切换队列SynchronousQueue:该队列传递任务到线程而不持有它们。在这一点上,试图向该队列压入一个任务,假如没有可用的线程立刻运行任务,那么就会入列失败,所以一个新的线程就会被创立。当解决那些内部依赖的任务集合时,这个选择可以避免锁住。直接接传递通常需要无边界的最大线程数来避免新提交任务被拒绝解决。当任务以平均快于被解决的速度提交到线程池时,它依次地确认无边界线程增长的可能性

使用无界队列LinkedBlockingQueue:使用这个队列的话,没有预先定义容量的无界队列,最大线程数是为corePoolSize,在核心线程都繁忙的时候会使新提交的任务在队列中等待被执行,所以将不会创立更多的线程,这时候,maximunPoolSize最大线程数的值将不起作用。当每个任务之间是相互独立的时比较适合该队列,任务之间不能互相影响执行。

使用有界队列ArrayBlockingQueue:使用这个队列,线程池中的最大线程数量就是maximunPoolSize,能够降低资源消耗,但是却使得线程之间调度变得更加困难,由于队列容量和线程池都规定完了。

假如想降低系统资源消耗,包括CPU使用率,操作系统资源消耗,上下文切换开销等等,可以设置一个较大的队列容量,较小的maximunPoolSize。假如线程经常发生阻塞,那么可以略微将maximunPoolSize设置大一点

  • keepAliveTime:线程没有任务执行最多保持多久时间终止。也就是当线程数量超过核心线程数量的时候,且小于最大线程数量,这一部分的线程在没有任务执行的时候是会保持直到超过keepAliveTime才会销毁

  • unit:keepAliveTime的时间单位

  • threadFactory:线程工厂,用来创立线程,当使用默认的线程工厂创立线程的时候,会使得线程具备相同优先级,并且设置了守护性,同时也设置线程名称

  • handler:拒绝策略。当workQueue满了,并且没有空闲的线程数,即线程达到最大线程数。就会有四种不同策略来解决

    1. 直接抛出异常(默认)

    2. 使用调用者所在线程执行任务

    3. 丢弃队列中最靠前的任务,并执行当前任务

    4. 直接丢弃当前任务

这四种就是对应的策略实现类(从上到下),是在ThreadPoolExecutor中的内部类

线程池五种状态

  • RUNNING:在这个状态的线程池能判断接受新提交的任务,并且也能解决阻塞队列中的任务

  • SHUTDOWN:处于关闭的状态,该线程池不能接受新提交的任务,但是可以解决阻塞队列中已经保存的任务,在线程处于RUNNING状态,调用shutdown()方法能切换为该状态。

  • STOP:线程池处于该状态时既不能接受新的任务也不能解决阻塞队列中的任务,并且能中断现在线程中的任务。当线程处于RUNNING和SHUTDOWN状态,调用shutdownNow()方法即可以使线程变为该状态

  • TIDYING:在SHUTDOWN状态下阻塞队列为空,且线程中的工作线程数量为0就会进入该状态,当在STOP状态下时,只需线程中的工作线程数量为0就会进入该状态。

  • TERMINATED:在TIDYING状态下调用terminated()方法就会进入该状态。可以认为该状态是最终的终止状态。

# execute()方法解析(JDK1.8)  execute():提交任务交给线程池运行public class ThreadPoolExecutor extends AbstractExecutorService {    public void execute(Runnable command) {        //任务为空则报空异常        if (command == null)            throw new NullPointerException();        /*         * 1. 假如正在运行的线程数少于corePoolSize。那么就尝试去开始一个新线程并用传入的command作为它第一个任务,      * 而后让addworker去原子性的检查线程池的运行状态和线程数量,以至于能提前知道能否能增加线程进去。      * 假如成功则线程运行,不成功就会去到下一步,并且再获取一次ctl的值(ctl是用来获取线程状态和线程数的)         */        int c = ctl.get();        if (workerCountOf(c) < corePoolSize) {            if (addWorker(command, true))                return;            c = ctl.get();        }        /*         * 2.假如一个任务能被成功的排队,那么依然需要双重检查能否我们需要增加一个新的线程(由于有可能会第一次检查完后有一个线程销毁,所以需要双重检查)      * 或者者进入此方法后线程关闭。所以很有必要重新检查一遍状态能否需要回滚排队,能否中止或者开始一个新线程或者能否不存在         */     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);        }         /*           *3.假如我们无法将任务进行排队(即进入队列中),那么我们尝试增加一个新线程,假如失败我们就使用拒绝策略拒绝这个任务          */        else if (!addWorker(command, false))            reject(command);    }}

线程池的少量常用方法

submit():提交任务,能够返回执行结果execute+Future

shutdown():关闭线程池,等待任务都执行完

shutdownNow():关闭线程池,不等待任务执行完

getTaskCount():线程池已执行和未执行的任务总数

getCompletedTaskCount():已完成的任务数量

getPoolSize():线程池当前线程数量

getActiveCount():当前线程池中正在执行任务的线程数量

Executor线程池创立的四种线程

  1. newFixedThreadPool:创立的是定长的线程池,可以控制线程最大并发数,超出的线程会在线程中等待,使用的是无界队列,核心线程数和最大线程数一样,当线程池中的线程没有任务时候立刻销毁,使用默认线程工厂。

  2. newSingleThreadExecutor:创立的是单线程化的线程池,只会用唯逐个个工作线程执行任务,可以指定按照能否是先入先出,还是优先级来执行任务。同样使用无界队列,核心线程数和最大线程数都是1个,同样keepAliveTime为0,可选择能否使用默认线程工厂。

  3. newCachedThreadPool:设定一个可缓存的线程池,当线程池长度超过解决的需要,可以灵活回收空闲线程,假如没有可以回收的才新建线程。没有核心线程数,当线程没有任务60s之后就会回收空闲线程,使用有界队列。同样可以选择能否使用默认线程工厂。

  4. newScheduledThreadPool:支持线程定时操作和周期性操作。

下面是方法的源码:

public class Executors {    public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }    public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>(),                                    threadFactory));    }    public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>(),                                      threadFactory);    }    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {        return new ScheduledThreadPoolExecutor(corePoolSize);    }    public static ScheduledExecutorService newScheduledThreadPool(            int corePoolSize, ThreadFactory threadFactory) {        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);    }}

转载于:http://www.cnblogs.com/Cubemen/p/10818895.html?utm_source=tuicool&utm_medium=referral

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » ThreadPoolExecutor线程池解析及Executor创立线程常见四种方式

发表回复