多线程

【多线程 】ThreadPoolExecutor 介绍

2023-10-18  本文已影响0人  Taurus_z
阿里巴巴Android开发手册对线程池使用的建议:

【推荐】ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放。
【强制】线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:
Executors 返回的线程池对象的弊端如下:
FixedThreadPool 和SingleThreadPool :允许的请求队列长度为
Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
CachedThreadPool 和ScheduledThreadPool :允许的创建线程数量为
Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

正例:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());

反例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

大部分情况我们使用Executors工具类去构建线程池,大概分为以下5种不同的线程池,如下
//构建线程数为1的线程池,等价于 newFixedThreadPool(1) 所构造出的线程池
Executors.newSingleThreadExecutor();
//构建包含固定线程数的线程池,默认情况下,空闲线程不会被回收
Executors.newFixedThreadPool(1);
//构建线程数不定的线程池,线程数量随任务量变动,空闲线程存活时间超过60秒后会被回收
Executors.newCachedThreadPool();
//构建核心线程数为 corePoolSize,可执行定时任务的线程池
Executors.newScheduledThreadPool(1);
//等价于 newScheduledThreadPool(1)
Executors.newSingleThreadScheduledExecutor();
ThreadManager 工具类,创建ThreadToolExecutor需要7个参数,如下
线程池-核心参数.png
/**
 * <p>
 * 1.降低资源消耗
 * 可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
 * 2.提高响应速度
 * 当任务到达时,任务可以不需要等到线程创建就能立即执行。
 * 3.提高线程的可管理性
 * 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
 * <p>
 * 最顶层的接口Executor仅声明了一个方法execute。
 * ExecutorService 接口在其父接口基础上,声明了包含但不限于shutdown、shutdownNow、submit、invokeAll等方法。
 * ScheduledExecutorService接口,
 * 则是声明了一些和定时任务相关的方法:schedule、scheduleAtFixedRate等。
 * 线程池的核心实现是在ThreadPoolExecutor类中,
 * 我们使用Executors调用newFixedThreadPool、newSingleThreadExecutor和newCachedThreadPool
 * 等方法创建线程池均是ThreadPoolExecutor类型。
 */
public class ThreadManager {
    private static final String TAG = "ThreadManager";

    /**
     * 根据cup核心数设置线程池数量
     * <p>
     * 《Android开发艺术探索》一书中建议:
     * 核心线程数等于CPU核心数+1;
     */
    private static final int corePoolSize = Runtime.getRuntime().availableProcessors();

    /**
     * 最大线程池数量= cpu核心数*2+1
     * <p>
     * 《Android开发艺术探索》一书中建议:
     * 线程池的最大线程数等于CPU的核心数的2倍+1;
     */
    private static final int maximumPoolSize = corePoolSize * 2 + 1;

    /**
     * 等待线程的存活时间
     * 核心线程无超时机制,分核心线程的闲置时间为4秒;
     */
    private static final long keepAliveTime = 30;

    /**
     * 等待线程存活时间的单位
     * <p>
     * TimeUnit.MINUTES 代表六十秒的时间单位
     */
    private static final TimeUnit unit = TimeUnit.MINUTES;

    /**
     * 3、 线程资源回收策略
     * 考虑到系统资源是有限的,对于线程池超出 corePoolSize 数量的空闲线程应进行回收操作。进行此操作存在一个问题,即回收时机。
     * 目前的实现方式是当线程空闲时间超过 keepAliveTime 后,进行回收。除了核心线程数之外的线程可以进行回收,核心线程内的空闲线程也可以进行回收。
     * 回收的前提是allowCoreThreadTimeOut属性被设置为 true,通过public void allowCoreThreadTimeOut(boolean) 方法可以设置属性值。
     * <p>
     * 4、 排队策略
     * 如上面线程创建规则所说的,当线程数量大于等于corePoolSize,workQueue未满时,则缓存新任务。
     * 这里要考虑使用什么类型的容器缓存新任务,通过 JDK 文档介绍,
     * 我们可知道有3种类型的容器可供使用,分别是同步队列,有界队列和无界队列。对于有优先级的任务,这里还可以增加优先级队列。
     * 以上所介绍的4种类型的队列,对应的实现类如下:
     * <p>
     * SynchronousQueue         同步队列        该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直阻塞
     * ArrayBlockingQueue       有界队列        基于数组的阻塞队列,按照 FIFO 原则对元素进行排序
     * LinkedBlockingQueue      无界队列        基于链表的阻塞队列,按照 FIFO 原则对元素进行排序
     * PriorityBlockingQueue    优先级队列   具有优先级的阻塞队列
     */
    private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();

    /**
     * 5、线程工厂。可通过工厂为新建的线程设置更有意义的名字
     */
    private static final ThreadFactory threadFactory = Executors.defaultThreadFactory();

    /**
     * 6、拒绝策略
     * 当线程池和任务队列均处于饱和状态时,使用拒绝策略处理新任务。默认是 AbortPolicy,即直接抛出异常
     * <p>
     * 如上线程创建规则策略中所说,当线程数量大于等于 maximumPoolSize,且 workQueue 已满,
     * 或者是当前线程池被关闭了则使用拒绝策略处理新任务。Java 线程池提供了4种拒绝策略实现类,
     * 如下:
     * <p></p>
     * AbortPolicy  丢弃新任务,并抛出 RejectedExecutionException
     * DiscardPolicy    不做任何操作,直接丢弃新任务
     * DiscardOldestPolicy  丢弃队列列首的元素,并执行新任务
     * CallerRunsPolicy 会在线程池当前正在运行的Thread线程池中处理被拒绝的任务
     */
    private static final RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

    private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);

    private ThreadManager() {
    }

    /**
     * 饿汉单例缓存线程池
     * 优点:对象优先创建,无须等待,效率高。
     * 缺点:申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。
     */
    private static final ThreadManager INSTANCE = new ThreadManager();

    public static ThreadManager get() {
        return INSTANCE;
    }

    /**
     * submit在执行过程中与execute不一样,不会抛出异常而是把异常保存在成员变量中,
     * 在FutureTask.get阻塞获取的时候再把异常抛出来
     */
    public void execute(Runnable runnable) {
        executor.execute(runnable);
    }

    /**
     * 通过Future可以很轻易地获得任务的执行情况,比如是否执行完成、是否被取消、是否异常等等
     */
    public Future<?> submit(Runnable runnable) {
        return executor.submit(runnable);
    }

    /**
     * 调用 shutdown 和 shutdownNow 方法关闭线程池后,就不能再向线程池提交新任务了。
     * 对于处于关闭状态的线程池,会使用拒绝策略处理新提交的任务。
     * <p>
     * shutdown 会将线程池的状态设置为SHUTDOWN,同时该方法还会中断空闲线程
     */
    public void shutdown() {
        executor.shutdown();
    }

    /**
     * shutdownNow 则会将线程池状态设置为STOP,并尝试中断所有的线程
     */
    public void shutdownNow() {
        executor.shutdownNow();
    }
}
/**
 * 饿汉单例缓存线程池
 * 优点:对象优先创建,无须等待,效率高。
 * 缺点:申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。
 */
public class ThreadUtils {

    private static final ThreadUtils INSTANCE = new ThreadUtils();
    private final ExecutorService threadPool = Executors.newCachedThreadPool();

    private ThreadUtils() {
        //构建线程数为1的线程池,等价于 newFixedThreadPool(1) 所构造出的线程池
        Executors.newSingleThreadExecutor();
        //构建包含固定线程数的线程池,默认情况下,空闲线程不会被回收
        Executors.newFixedThreadPool(1);
        //构建线程数不定的线程池,线程数量随任务量变动,空闲线程存活时间超过60秒后会被回收
        Executors.newCachedThreadPool();
        //构建核心线程数为 corePoolSize,可执行定时任务的线程池
        Executors.newScheduledThreadPool(1);
        //等价于 newScheduledThreadPool(1)
        Executors.newSingleThreadScheduledExecutor();
    }

    public static ThreadUtils get() {
        return INSTANCE;
    }

    public Future<?> submit(Runnable runnable) {
        if (runnable != null) {
            return threadPool.submit(runnable);
        }
        return null;
    }
}
上一篇下一篇

猜你喜欢

热点阅读