【多线程 】ThreadPoolExecutor 介绍

阿里巴巴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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容