ThreadPoolExecutor线程池的简单介绍

在编程中经常会使用线程来异步处理任务,但是每个线程的创建和销毁都需要一定的开销。如果每次执行一个任务都需要开一个新线程去执行,则这些线程的创建和销毁将消耗大量的资源,并且线程都是“各自为政”的,很难对其进行控制,更何况有一堆的线程在执行。这时就需要线程池来对线程进行管理,通过线程池中线程的复用,减少创建和销毁线程的性能开销,也能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。

1、为什么要使用线程池
1.1不断创建线程的弊端:
  • 创建了一个线程并执行,它在任务结束后GC会自动回收该线程
  • 大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
  • 大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
  • 线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
1.2使用线程池管理线程优点
  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
  • 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行
  • 方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或OOM等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率
  • 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
2、线程池的处理流程
NO
YES
NO
YES
NO
YES
提交任务
线程数是否达到croePoolSize
创建核心线程执行任务
任务队列是否已满
将任务加到队列中
线程数是否达到最大线程数
创建非核心线程执行任务
执行饱和策略
3、线程池的种类
3.1 FixedThreadPool 可重用固定线程数的线程池

在Executors类中提供了创建FixedThreadPool的方法:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

FixedThreadPool的corePoolSize和maximunPoolSize都设置为创建FixedThreadPool指定的参数nThreads,也就是FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime设置为0L,意味着多余的线程会被立即终止。因为不会产生多余的线程,所以keepAliveTime是无效的参数。任务队列采用了无界的阻塞队列LinkedBlockingQueue。当线程数超过corePoolSize时,就将任务存储在任务队列中,当线程池有空闲线程时,则从任务队列中去取任务执行。

简单使用示例:

		//1. 创建定长线程池对象 & 设置线程池线程数量固定为3
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        //2. 创建好Runnable类线程对象 & 需执行的任务
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务啦");
            }
        };
        //3. 向线程池提交任务
        fixedThreadPool.execute(task);
        //4. 关闭线程池
        fixedThreadPool.shutdown();
3.2 CachedThreadPool 根据需要创建线程的线程池

创建CachedThreadPool的代码如下所示:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    }

CachedThreadPool的corePoolSize为0,maximunPoolSize设置为Integer.MAX_VALUE,这就意味着CacheThreadPool没有核心线程,非核心线程是无界的。keepAliveTime设置为60L,则空闲线程等待新任务的最长时间为60s。在此用了阻塞队列SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。

简单使用示例:

		// 1. 创建可缓存线程池对象
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        // 2. 创建好Runnable类线程对象 & 需执行的任务
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("执行任务啦");
            }
        };
        // 3. 向线程池提交任务
        cachedThreadPool.execute(task);
        // 4. 关闭线程池
        cachedThreadPool.shutdown();
        //当执行第二个任务时第一个任务已经完成
        //那么会复用执行第一个任务的线程,而不用每次新建线程。
3.3 SingleThreadExecutor 使用单个工作线程的线程池

创建SingleThreadExecutor的代码如下所示:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

corePoolSize和maximumPoolSize都为1,意味着SingleThreadExecutor只有一个核心线程,其他参数都和FixedThreadPool一样。
简单使用示例:

		// 1. 创建单线程化线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 2. 创建好Runnable类线程对象 & 需执行的任务
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("执行任务啦");
            }
        };
        // 3. 向线程池提交任务
        singleThreadExecutor.execute(task);
        // 4. 关闭线程池
        singleThreadExecutor.shutdown();
3.4 ScheduledThreadPool 实现定时和周期性任务的线程池

创建ScheduledThreadPool的代码如下所示:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

ScheduledThreadPool继承自ThreadPoolExecutor,它主要用于给定延时之后的运行任务或者定期处理任务。ScheduledThreadPoolExectour的构造方法如下:

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

从代码可以看出,ScheduledThreadPoolExecutor的构造方法最终调用的是ThreadPoolExecutor的构造方法。corePoolSize是传进来的固定数值,maximumPoolSize的值是Integer.MAX_VALUE。因为采用的是DelayedWorkQueue是无界的,所以maximumPoolSize这个参数是无效的。

简单使用示例:

		// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        // 2. 创建好Runnable类线程对象 & 需执行的任务
        Runnable task =new Runnable(){
            public void run(){
                System.out.println("执行任务啦");
            }
        };
        // 3. 向线程池提交任务
        scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
        scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
        // 4. 关闭线程池
        scheduledThreadPool.shutdown();

学了以上四种类型的线程池,准备在Android studio 工具进行编码测试时,竟然出现红线报错提示:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。(前提是已安装阿里编码规约插件)。
也就是说,以上四种创建线程池的方式,是不符合编程规范的。

原因在于:

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

参数作用如下所示:

  • corePoolSize:核心线程数。默认情况下线程池是空的,只有任务提交时才会创建线程。如果当前运行的线程数少于corePoolSize,则会创建新线程来处理任务;如果等于或者多于corePoolSize,则不再创建。
  • maximumPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximumPoolSize,则线程池仍旧会创建新的线程来处理任务。
  • keepAliveTime:非核心线程闲置的超时时间。超过这个时间则回收
  • TimeUnit:keepAliveTime参数的时间单位
  • workQueue:任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,也就是阻塞队列。
  • ThreadFactory:线程工厂
  • RejectedExecutionHandler:饱和策略

(1)AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException。

(2))CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

(3)DiscardPolicy:不能执行的任务,并将该任务删除。

(4)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。

ThreadPoolExecutor创建线程池示例:

public class ThreadPoolProxy {

    private volatile ThreadPoolExecutor mExecutor;
    /**
     * 核心线程数
     */
    private int mCorePoolSize;
    /**
     * 最大线程数
     */
    private int mMaximumPoolSize;
    /**
     * 保持时间
     */
    private long mKeepAliveTime;

    public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
        super();
        mCorePoolSize = corePoolSize;
        mMaximumPoolSize = maximumPoolSize;
        mKeepAliveTime = keepAliveTime;

    }

    /**
     * 创建唯一的线程池
     */
    private ThreadPoolExecutor initThreadPoolExecutor() {

        if (null == mExecutor) {
            synchronized (ThreadPoolProxy.class) {
                if (null == mExecutor) {
                    TimeUnit unit = TimeUnit.MILLISECONDS;
                    //(无界队列)可变的阻塞队列
                    BlockingQueue<Runnable> workQueue = new LinkedBlockingDeque<>();
                    //默认线程工厂
                    ThreadFactory threadFactory = Executors.defaultThreadFactory();
                    //异常捕获器,弃任务并抛出RejectedExecutionException异常。
                    RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
                    mExecutor = new ThreadPoolExecutor(
                            mCorePoolSize,
                            mMaximumPoolSize,
                            mKeepAliveTime,
                            unit,
                            workQueue,
                            threadFactory,
                            handler);
                }
            }
        }
        return mExecutor;
    }

    /**
     * 执行任务
     *
     * @param task
     */
    public void execute(Runnable task) {
        initThreadPoolExecutor();
        mExecutor.execute(task);
    }


    /**
     * 移除未执行任务,已经执行的不会被移除
     *
     * @param task
     */
    public void remove(Runnable task) {
        initThreadPoolExecutor();
        mExecutor.remove(task);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值