线程池详解及工具类

线程池详解

线程池的优势

1)减少消耗:降低线程创建和销毁造成的消耗;
2)响应快:通过复用已存在的线程,无需等待新线程的创建便能立即执行;
3)降低OOM:方便线程并发数的管控。过多的创建线程,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换增加时间消耗;
4)线程管理:线程的延时执行、定时循环执行等。

线程池的参数

public ThreadPoolExecutor(int corePoolSize,//常用
                          int maximumPoolSize,//常用
                          long keepAliveTime,//常用
                          TimeUnit unit,//常用
                          BlockingQueue<Runnable> workQueue,//常用
                          ThreadFactory threadFactory,//不常用
                          RejectedExecutionHandler handler)//不常用
1、corePoolSize

要保留在池中的线程数,即使它们处于空闲状态,也不清除。
除非设置了allowCoreThreadTimeOut=true,此时超过一定时间(时长下面参数决定keepAliveTime),
就会被销毁掉。

2、maximumPoolSize

池中允许的最大线程数>=核心线程数+非核心线程数(线程数量>核心线程数+队列极限数时开始创建非核心线程)

3、keepAliveTime

当线程数大于核心时,这是多余空闲线程在终止之前等待新任务的最长时间

4、unit

keepAliveTime的单位

5、workQueue

在执行任务之前用于保存任务的队列。此队列将仅包含由{@code execute}方法提交的{@code Runnable}任务。

6、threadFactory

执行器创建新线程时使用的工厂

7、handler

由于达到线程边界和队列容量而阻止执行时要使用的处理程序,抛异常用的,即使不设置也有默认的

线程池的内部逻辑

核心线程池是否已满
队列是否已满
最大线程池是否已满
执行饱和策略
创建核心线程执行任务
将任务存储在队列中
创建线程执行任务

线程池的常用方法

1、缓存线程池,就是有空闲的已创建的线程就复用,没有空闲的就创建
Executors.newCachedThreadPool()
//对应的代码
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

创建一个线程池,该线程池根据需要创建新线程,但在以前构造的线程可用时将重用它们。
这些池通常会提高执行许多短期异步任务的程序的性能。
调用{@code execute}将重用以前构造的线程(如果可用)。
如果没有可用的现有线程,将创建一个新线程并将其添加到池中。
已60秒未使用的线程将被终止并从缓存中移除。因此,空闲时间足够长的池不会消耗任何资源。
请注意,可以使用{@link ThreadPoolExecutor}构造函数创建具有相似属性但具有不同详细信息
(例如,超时参数)的池。

2、定长线程池,可控制线程最大并发数,超出nThreads则在队列等待,LinkedBlockingQueue导致maximumPoolSize失效
Executors.newFixedThreadPool();
//对应的代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

创建一个线程池,该线程池重用在共享的无边界队列上运行的固定数量的线程。
在任何时候,最多{@code nThreads}个线程将是活动的处理任务。
如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待线程可用。
如果任何线程在关闭之前的执行过程中由于失败而终止,则在需要执行后续任务时将使用新线程来代替它。
池中的线程将一直存在,直到显式地{@link ExecutorService#shutdown shutdown}。

3、延迟线程池,支持定时及周期性任务执行,DelayedWorkQueue起作用
Executors.newScheduledThreadPool();
//对应的代码
public ScheduledThreadPoolExecutor(int corePoolSize) {
    //super是ThreadPoolExecutor
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

创建一个线程池,该线程池可以安排命令在给定延迟后运行或定期执行。
@param corePoolSize要保留在池中的线程数,即使它们处于空闲状态

4、单线程化的线程池,它只会用唯一的工作线程来执行任务,

保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,LinkedBlockingQueue导致maximumPoolSize失效

Executors.newSingleThreadExecutor();
//对应的代码
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

创建一个执行器,该执行器使用单个工作线程在无边界队列上操作。
(但是,请注意,如果此单线程在关闭之前的执行过程中由于失败而终止,
则在需要执行后续任务时将替换新线程。)任务保证按顺序执行,
并且在任何给定时间都不会有多个任务处于活动状态。
与其他等价的{@code newFixedThreadPool(1)}不同,
返回的执行器保证不可重新配置以使用其他线程。

常用的workQueue类型:

1)SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
2)LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
3)ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
4)DelayedWorkQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

四种拒绝策略:(也可以自定义拒绝策略)

1)AbortPolicy:(中止策略)默认的拒绝策略。直接抛出异常。
2)CallerRunsPolicy:(调用者运行策略)在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。(可能会把任务抛到主线程)
3)DiscardPolicy:(丢弃策略)会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
4)DiscardOldestPolicy:(弃老策略)当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加到等待队列中进去。

线程池工具类

/**
 * author : gujun
 * date   : 2020/8/14 13:28
 * desc   : 线程池工具类,避免过多的创建线程
 */
public class ThreadPoolUtil {

    //参数初始化,CPU个数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //核心线程数量大小
    private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    //线程池最大容纳线程数,Android 应用的话应该是属于IO密集型应用,所以数量一般设置为 2N+1
    private static final int maximumPoolSize = CPU_COUNT * 2 + 1;
    //线程空闲后的存活时长
    private static final int keepAliveTime = 30;

    private static ThreadPoolExecutor executor = null;

    private static ThreadPoolExecutor getExecutor() {
        if (executor == null) {
            synchronized (ThreadPoolUtil.class) {
                if (executor == null) {
                    executor = new ThreadPoolExecutor(
                            corePoolSize,
                            maximumPoolSize,
                            keepAliveTime,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<Runnable>(Integer.MAX_VALUE),//任务过多后,存储任务的一个阻塞队列
                            new ThreadFactory() {
                                private final AtomicInteger mCount = new AtomicInteger(1);

                                public Thread newThread(Runnable r) {
                                    return new Thread(r, "IMThreadPoolUtil #" + mCount.getAndIncrement());
                                }
                            },//线程的创建工厂,默认
                            new ThreadPoolExecutor.DiscardOldestPolicy()//线程池任务满载后采取的任务拒绝策略
                    );
                    //设置核心线程空闲时间超过keepAliveTime值时释放线程
                    executor.allowCoreThreadTimeOut(true);
                }
            }
        }
        return executor;
    }

    /**
     * 无返回值直接执行
     *
     * @param runnable
     */
    public static void execute(Runnable runnable) {
        getExecutor().execute(runnable);
    }

    /**
     * 返回值Future可控制异步回调状态,如果调用get()方法则会阻塞线程直到异步执行完返回值
     *
     * @param callable
     */
    public static <T> Future<T> submit(Callable<T> callable) {
        return getExecutor().submit(callable);
    }

}

工具类和四种常见线程池的使用Demo地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值