美文网首页多线程
【多线程 】ThreadPoolExecutor 介绍

【多线程 】ThreadPoolExecutor 介绍

作者: Taurus_z | 来源:发表于2023-10-18 22:11 被阅读0次
    阿里巴巴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;
        }
    }
    

    相关文章

      网友评论

        本文标题:【多线程 】ThreadPoolExecutor 介绍

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