线程池(Executor)


线程池的好处:

第一:降低资源消耗。通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以可以不用等待线程的创建就能立即执行。

第三:提高线程的可管理性,线程是稀缺资源,不能无限制的创建。

额外好处:有效避免this逃逸问题:this逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误, 因此应该避免this逃逸的发生。(this逃逸经常发生在构造函数中启动线程或注册监听器时)

线程池的基本架构:

Executor:

public interface Executor {
    void execute(Runnable command);
}

ExcutorService:

继承Executor,它是“执行者服务”接口,它是为”执行者接口Executor”服务而存在的。准确的地说,ExecutorService提供了”将任务提交给执行者的接口(submit方法)”,”让执行者执行任务(invokeAll, invokeAny方法)”的接口等等。

public interface ExecutorService extends Executor {

    void shutdown();
    List<Runnable> shutdownNow();

    /**
     * 如果此执行程序已关闭,则返回 true。
     */
    boolean isShutdown();

    /**
     * 如果关闭后所有任务都已完成,则返回 true
     */
    boolean isTerminated();

    /**
     * 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    /**
     * 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • execute()和submit()方法?

使用这两个方法向线程池提交任务。execute()用于提交不需要返回值的任务(执行任务需要实现的Runnable接口或Callable接口),submit()方法用于提交需要返回值的任务,会返回一个future对象,通过这个对象可以判断任务是否执行成功,通过future.get()方法来获取返回值,除了普通的返回值,submit.get()也可以捕获任务执行过程中的异常,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

  • 关于关闭线程池

shutdownNow,尝试停止所有正在执行或暂停任务的线程,返回等待执行任务的列表

shutdown,不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

AbstractExecutorService

抽象类,实现ExecutorService接口,为其提供默认实现。AbstractExecutorService除了实现ExecutorService接口外,还提供了newTaskFor()方法返回一个RunnableFuture,在运行的时候,它将调用底层可调用任务,作为 Future 任务,它将生成可调用的结果作为其结果,并为底层任务提供取消操作

ScheduledExecutorService

继承ExcutorService,为一个“延迟”和“定期执行”的ExecutorService。他提供了一些如下几个方法安排任务在给定的延时执行或者周期性执行。

// 创建并执行在给定延迟后启用的 ScheduledFuture。
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

// 创建并执行在给定延迟后启用的一次性操作。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

// 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
//也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

// 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

线程池的处理流程:

1.线程池首先判断核心线程池的里线程是否都在执行任务,如果不是,就会创建一个新的工作线程来执行任务,如果是,进入下一个流程。

2.判断工作队列是否满,工作队列没有满,将新提交的任务存储在工作队列里,如果满了,进入下一个流程。

3.判断线程池的线程是否都处于工作状态,如果没有,就创建新的工作线程来执行任务。如果满了,就交给饱和策略来处理这个任务。

关于创建线程池

方式一:

通过Executor 框架的工具类Executors来实现我们可以创建三种类型的ThreadPoolExecutor:

FixedThreadPool:

初始化一个指定线程数的线程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程,这是在相等的情况下,在两者不同的情况下,线程池的线程数大于corePoolSize时,多余的线程会有一个时间,超过这个时间以后多余的线程会被终止,适用于任务量比较固定但耗时长的任务。。

该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

SingleThreadExecutor:

方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。这类线程池适用于多个任务顺序执行的场景。

CachedThreadPool:

该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。但它其中的空闲线程等待时间最大时长60s,因为它是大小无界的线程池,适用于执行很多的短期异步任务的小程序

ScheduledThreadPool:

它主要用来在给定延迟之后执行任务,或者定期执行任务。

  • (1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduleFutureTask。
  • (2)线程池中的线程从DelayQueue中获取ScheduleFutureTask,然后执行任务。
  • ScheduledThreadPoolExecutor:适用于多个后台线程执行周期性任务,同时为了满足资源管理的需求而需要限制后台线程数量的应用场景。
  • SingleThreadScheduledExecutor:适用于需要单个后台线程执行周期任务,同时需要保证任务顺序执行的应用场景。

方式二:

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建(ExecutorService executor = Executors.newFixedThreadPool(5)),而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

ThreadPoolExecutor参数:

corePool:核心线程池的大小。

maximumPool:最大线程池的大小。

BlockingQueue:用来暂时保存任务的工作队列。

RejectedExecutionHandler:达到最大线程且工作队列已满,execute()方法将要调用的Handler。

keepAliveTime:线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效,当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置成true时,他会作用于核心线程,否则是作用于非核心线程。

threadFactory:线程工厂。为线程池提供创建新线程的功能。

unit:keepAliveTime的单位。TimeUnit

Executors 返回线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadExecutor: 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
  • CachedThreadPool 和 ScheduledThreadPool: 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

通过构造方法实现。

通过构造方法明确的指定任务队列的最大长度,防止OOM,同时也要明确拒绝任务时的行为。

对于配置线程池的大小:

如果是CPU密集型任务,就需要尽量压榨CPU,CPU的占用率已经很高了,再开更多的线程也没啥用,参考值可以设为NCPU+1

  如果是IO密集型任务,CPU占用率不高,很多的CPU都处于闲置的状态,所以可以设置很多的线程,参考值可以设置为2*NCPU

关于线程池的饱和拒绝策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

FutureTask:

当一个线程需要等待另外一个线程把某个任务执行完后才能继续执行,就可以使用FutureTask,主要是利用它的get()方法具有阻塞作用,只有等执行完成以后才会返回。

https://www.jianshu.com/p/a369536f79d7?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

优质内容筛选与推荐>>
1、iOS GCD使用指南
2、嵌套HashTable 遍历
3、URAL1326. Bottle Taps(状压)
4、HDOJ--2680--Choose the best route
5、带权中位数/sgu 114 Telecasting station


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn