多线程之线程池基本内容 Android 多线程:线程池理解和使用总结 Android 线程池原理及使用 一:使用线程池的原因 二:几种常见的线程池 三:JAVA中的阻塞队列 四:各个线程池总结及适用场景 五:线程池其它方法:

多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:

一、Android线程池介绍

1.1 原理

  Android中的线程池概念来源于Java中的Executor,Executor是一个接口,真正的线程的实现为ThreadPoolExecutor。

(ThreadPoolExecutor继承了AbstractExecutorService,AbstractExecutorService是ExecutorService的实现类,ExecutorService继承了Executor接口)。

1.2 分类

从线程池的功能特性上来说,Android中线程池主要分为4类:

  • FixedThreadPool(线程数量固定)、
  • CachedThreadPool(线程数量不定)、
  • ScheduledThreadPool(核心线程数量固定,非核心线程数量无限制)、
  • SingleThreadExecutor(只有一个核心线程,所有任务在里面按顺序执行)。

1.3 优点

  1. 重用线程池中的线程,避免频繁创建和销毁线程所带来的内存开销。
  2. 有效控制线程的最大并发数,避免因线程之间抢占资源而导致的阻塞现象。
  3. 能够对线程进行简单的管理,提供定时执行 以及 指定时间间隔循环执行等功能。
线程池的作用:

线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程 排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程 池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:
  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

在Android开发中,如果我们要执行某个耗时任务,一般都会考虑开启一个线程去处理。
因为我们都知道一个线程run方法执行完毕后,才算真正结束,但是,这只是结束,并没有被回收,会一直闲置在那里,等待GC去回收,所以如果每执行一个任务,我们都new一个线程,那么在某些极端的场景下,是比较消耗内存的。
之前的内存优化的文章中,我讲过关于android中的池的概念,也就是复用的机制,那么对于线程也有个线程池。
 

 

二、 Android线程池分类

由于Android中的线程池都是直接 或间接 通过配置ThreadPoolExecutor来实现的,因此在介绍它们之前要先介绍ThreadPoolExecutor。

2.1 ThreadPoolExecutor介绍

ThreadPoolExecutor有多个构造方法以及参数,下面介绍比较常用的构造方法。

 public ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,
                              long keepAliveTime, TimeUnit unit,
                              BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);
    }
  • corePoolSize:线程池的核心线程数,默认情况下核心线程会一直存活于线程池,即使处于闲置状态。如果设置了ThreadPoolExecutor里的allowCoreThreadTimeOut为True,则核心线程在等待新任务到来时会有超时终止策略。超时的时间间隔由keepAliveTime所决定。
    private volatile long keepAliveTime;
    private volatile boolean allowCoreThreadTimeOut;
  • maximumPoolSize:线程池所能容纳的最大线程数,当线程数达到这个数值后,后续的新任务将会被阻塞。
  • keepAliveTime:非核心线程超时时长,超过这个时间没有任务执行,非核心线程就会被回收。当核心线程设置allowCoreThreadTimeOut为true时,keepAliveTime同样作用于核心线程。
  • unit:用于指定keepAliveTime参数的时间单位,这是一个枚举
    ,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECOUNDS(秒)以及TimeUnit.MINUTS(分钟)等,并且每个变量提供了转换函数方便各个单位数据的转换。
  • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
  • threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r);
  • handler:该handler的类型为RejectedExecutionHandler。这个参数不常用,它的作用是当线程池无法执行新任务时,会调用handler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法来抛出异常。无法执行的原因可能是由于队列满或者任务无法成功执行等。
    ThreadPoolExecutor执行任务大致遵循以下规则:
    (1)如果线程池中的线程数量未达到核心线程的数量,会直接启动一个核心线程来执行任务。
    (2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
    (3)如果第2步中无法插入新任务,说明任务队列已满,如果未达到规定的最大线程数量,则启动一个非核心线程来执行任务。
    (4)如果第3步中线程数量超过规定的最大值,则拒绝任务并使用RejectedExecutionHandler的rejectedExecution(Runnable r, ThreadPoolExecutor e)方法来通知调用者。

2.2 线程池的分类

先介绍一个类Executors,这个类定义了各种参数用于创建多种线程池,最终实现还是通过ThreadPoolExecutor类。

1. FixedThreadPool (Fixed:固定的,不变的)
通过Executors的newFixedThreadPool创建,通过创建时的参数可以看出又以下几个特点:

    • 线程数量固定且都是核心线程:核心线程数量和最大线程数量都是nThreads;
    • 都是核心线程且不会被回收,快速响应外界请求;
    • 没有超时机制,任务队列也没有大小限制;
    • 新任务使用核心线程处理,如果没有空闲的核心线程,则排队等待执行。
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }
Android sdk的源码中用的比较多的,它的池子线程数有个最大值,可以自己设置。如果超过这个最大值,那么任务就会加入任务队列去等待。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
fixedThreadPool.execute(new Runnable() {... });

2. CachedThreadPool (Cached:缓存)
通过Executors的newCachedThreadPool创建,特点:

  • 线程数量不定,只有非核心线程,最大线程数任意大:传入核心线程数量的参数为0,最大线程数为Integer.MAX_VALUE;
  • 有新任务时使用空闲线程执行,没有空闲线程则创建新的线程来处理。
  • 该线程池的每个空闲线程都有超时机制,时常为60s(参数:60L, TimeUnit.SECONDS),空闲超过60s则回收空闲线程。
  • 适合执行大量的耗时较少的任务,当所有线程闲置超过60s都会被停止,所以这时几乎不占用系统资源。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    }

这种线程池比较灵活,也就是说它的池里的线程数量并不是固定的,理论上可以无限大,任务不需要排队。
如果有空闲的线程,则复用,无则新建线程。
ExecutorService cachedThreadPool =Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {.....});

 

3. ScheduledThreadPool(Scheduled:预定的、排定的)--用来创建一个定长线程池,并且支持定时和周期性的执行任务。
通过Executors的newScheduledThreadPool创建,特点:

  • 核心线程数量固定,非核心线程数量无限制;
  • 非核心线程闲置超过10s会被回收;
  • 主要用于执行定时任务和具有固定周期的重复任务;
  • 四个里面唯一一个有延时执行和周期重复执行的功能:创建时
    ScheduledThreadPoolExecutor(corePoolSize)返回的是new ScheduledThreadPoolExecutor对象,
  • ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,DelayedWorkQueue是ScheduledThreadPoolExecutor的一个静态内部类,主要用于处理任务队列延迟的工作。
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    //默认闲置超时回收时常
    private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
    }

4. SingleThreadExecutor (单线程线程池)
通过Executors.newSingleThreadExecutor()创建,特点:

  • 只有一个核心线程,所有任务在同一个线程按顺序执行。
  • 所有的外界任务统一到一个线程中,所以不需要处理线程同步的问题。

用来创建一个单线程化的线程池,它只用唯一的工作线程来执行任务,一次只支持一个,所有任务按照指定的顺序执行。
这是一个单例化的线程池,他只有一个线程去执行任务。最常见的一个例子就是我们的UI线程,它就是典型的单线程模型。

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

三、 Android线程池简单使用

3.1 线程池简单使用

(1)上面所说的四种常用线程池的实例化:

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
             //do something
        }
    };
     
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
    fixedThreadPool.execute(runnable);
    
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    cachedThreadPool.execute(runnable);
  
//线程4 // 注意这里创建的是ScheduledExecutorService对象,ScheduledExecutorService是ExecutorService的子类 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); // 延迟1000ms后执行runnable scheduledThreadPool.schedule(runnable,1000,TimeUnit.MILLISECONDS);//定时执行
// 延迟1000ms后,每2000ms执行一次runnable scheduledThreadPool.scheduleAtFixedRate(runnable,1000,2000,TimeUnit.MILLISECONDS);//周期执行 isRunning = true;

(2)简单使用小demo:
添加线程池开始执行和ScheduledThreadPool停止执行的两个按钮点击事件,布局就不贴了。

findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            count = 0;
            mStatueText.setText("线程开始执行,次数:"+ count);
            startThreadPool();
        }
    });
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(null != scheduledThreadPool && isRunning){ scheduledThreadPool.shutdown(); mStatueText.setText("scheduledThreadPool线程停止,当前次数:"+ count); isRunning = false; } } });

startThreadPool(); 方法里Runnable每执行一次增加一次count并打到TextView上:

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
                count++;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mStatueText.setText("线程执行完毕,次数:"+ count);
                    }
                });
        }
    };

接着让所有的线程池执行这个Runnable对象,最后的结果是变量count的值从0直接到了3,然后又到5最后无限增长。
原因是除ScheduledThreadPool对象外的三个线程池很快执行了任务,ScheduledThreadPool对象的执行了两次任务,两个都延时1000ms。

最后循环执行的任务一直在增加count的值。

相关文章:
Android多线程:理解和简单使用总结

=======

Android 线程池原理及使用

一:使用线程池的原因

在android开发中经常会使用多线程异步来处理相关任务,而如果用传统的newThread来创建一个子线程进行处理,会造成一些严重的问题:

1:在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。

2:多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。

3:多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。

总而言之:频繁地为每一个任务创建一个线程,缺乏统一管理,降低性能,并且容易出现问题。

为了解决这些问题,就要用到今天的主角——线程池.

线程池使用的好处:

1:对多个线程进行统一地管理,避免资源竞争中出现的问题。

2:(重点):对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。

3:JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。

二:几种常见的线程池

1:ThreadPoolExecutor 创建基本线程池

创建线程池,主要是利用ThreadPoolExecutor这个类,而这个类有几种构造方法,其中参数最多的一种构造方法如下:

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        ...
    }

corePoolSize: 该线程池中核心线程的数量。

maximumPoolSize:该线程池中最大线程数量。(区别于corePoolSize)

keepAliveTime:从字面上就可以理解,是非核心线程空闲时要等待下一个任务到来的时间,当任务很多,每个任务执行时间很短的情况下调大该值有助于提高线程利用率。注意:当allowCoreThreadTimeOut属性设为true时,该属性也可用于核心线程。

unit:上面时间属性的单位

workQueue:任务队列,后面详述。

threadFactory:线程工厂,可用于设置线程名字等等,一般无须设置该参数。

设置好几个参数就可以创建一个基本的线程池,而之后的各种线程池都是在这种基本线程池的基础上延伸的。

下面贴个自己写的demo来熟悉具体的使用并且加深影响:

//创建基本线程池
        final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(100));

设置一个按钮mThreadPoolExecute,并在点击事件中使用线程池

 /**
  * 基本线程池使用
  */
 mThreadPoolExecute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                                Log.d("当前线程:",Thread.currentThread().getName());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    threadPoolExecutor.execute(runnable);
                }
            }
        });

结果会每2s打印三个日志。

具体过程:

1.execute一个线程之后,如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行。

2.execute一个线程之后,如果线程池中的线程数已经达到核心线程数,且workQueue未满,则将新线程放入workQueue中等待执行。

3.execute一个线程之后,如果线程池中的线程数已经达到核心线程数但未超过非核心线程数,且workQueue已满,则开启一个非核心线程来执行任务。

4.execute一个线程之后,如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常。

demo中设置的任务队列长度为100,所以不会开启额外的5-3=2个非核心线程,如果将任务队列设为25,则前三个任务被核心线程执行,剩下的30-3=27个任务进入队列会满,此时会开启2个非核心线程来执行剩下的两个任务。

//新开启了thread-4与thread-5执行剩下的超出队列的两个任务28和29
2019-03-28 15:54:07.879 22284-22618/com.example.threadpooltest D/Thread:: 1
2019-03-28 15:54:07.879 22284-22617/com.example.threadpooltest D/Thread:: 0
2019-03-28 15:54:07.879 22284-22617/com.example.threadpooltest D/当前线程:: pool-1-thread-1
2019-03-28 15:54:07.879 22284-22618/com.example.threadpooltest D/当前线程:: pool-1-thread-2
2019-03-28 15:54:07.880 22284-22619/com.example.threadpooltest D/Thread:: 2
2019-03-28 15:54:07.880 22284-22619/com.example.threadpooltest D/当前线程:: pool-1-thread-3
2019-03-28 15:54:07.881 22284-22620/com.example.threadpooltest D/Thread:: 28
2019-03-28 15:54:07.881 22284-22620/com.example.threadpooltest D/当前线程:: pool-1-thread-4
2019-03-28 15:54:07.881 22284-22621/com.example.threadpooltest D/Thread:: 29
2019-03-28 15:54:07.881 22284-22621/com.example.threadpooltest D/当前线程:: pool-1-thread-5

疑问:每个for循环里都有一个sleep(2000),为何会每隔2s打印三个任务?
原因:因为一开始的时候只是声明runnable对象并且重写run()方法,并没有运行,而后execute(runnable) 才会sleep,又因为一开始创建线程池的时候声明的核心线程数为3,所以会首先开启三个核心线程,然后执行各自的run方法,虽然有先后顺序,但这之间的间隔很短,所以2s后同时打印3个任务。

2:FixedThreadPool (可重用固定线程数)

Executors类中的创建方法:


 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
FixedThreadPool创建

特点:参数为核心线程数,只有核心线程,无非核心线程,并且阻塞队列*。

demo代码:

创建:

  //创建fixed线程池

  final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

使用:

    /**
     * fixed线程池
     */
        mFixedPoolThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                                Log.d("当前线程:",Thread.currentThread().getName());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    fixedThreadPool.execute(runnable);

                }
            }
        });

结果为每2s打印5次任务,跟上面的基础线程池类似。

3:CachedThreadPool (按需创建)

Executors类中的创建方法:


 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
CachedThreadPool创建

特点:没有核心线程,只有非核心线程,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列。

;
        mCachedPoolThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    cachedThreadPool.execute(runnable);

                }
            }
        });

结果:过2s后直接打印30个任务

结果分析:

  • 因为没有核心线程,其他全为非核心线程,SynchronousQueue是不存储元素的,每次插入操作必须伴随一个移除操作,一个移除操作也要伴随一个插入操作。
  • 当一个任务执行时,先用SynchronousQueue的offer提交任务,如果线程池中有线程空闲,则调用SynchronousQueue的poll方法来移除任务并交给线程处理;如果没有线程空闲,则开启一个新的非核心线程来处理任务。
  • 由于maximumPoolSize是*的,所以如果线程处理任务速度小于提交任务的速度,则会不断地创建新的线程,这时需要注意不要过度创建,应采取措施调整双方速度,不然线程创建太多会影响性能。
  • 从其特点可以看出,CachedThreadPool适用于有大量需要立即执行的耗时少的任务的情况。

4:SingleThreadPool(单个核线的fixed)

创建方法:


 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
SingleThreadPool创建

创建:

 //创建Single线程池
 final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

使用:

  /**
    * single线程池
    */
        mSinglePoolExecute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int finali = i;x
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(2000);
                                Log.d("Thread", "run: "+finali);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    singleThreadExecutor.execute(runnable);

                }
            }
        });

结果:每2s打印一个任务,由于只有一个核心线程,当被占用时,其他的任务需要进入队列等待。

5:ScheduledThreadPool(定时延时执行)

创建方法:


 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
ScheduledThreadPool创建1

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
ScheduledThreadPool创建2

创建:

//创建Scheduled线程池
  final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

使用:

  /**
    * scheduled线程池
    */
        mScheduledTheadPool.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {

                            Log.d("Thread", "This task is delayed to execute");
                    }

                    };
                    scheduledThreadPool.schedule(runnable,10,TimeUnit.SECONDS);//延迟启动任务

//延迟5s后启动,每1s执行一次             scheduledThreadPool.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//启动后第一次延迟5s执行,后面延迟1s执行  scheduledThreadPool.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);
            }
        });

结果如代码所述。

6:自定义的PriorityThreadPool(队列中有优先级比较的线程池)

创建:

 //创建自定义线程池(优先级线程)
final ExecutorService priorityThreadPool = new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,new PriorityBlockingQueue<Runnable>());

自定义Runnable,继承Comparable接口:

public abstract class PriorityRunnable implements Runnable,Comparable<PriorityRunnable> {
    private int priority;

    public  PriorityRunnable(int priority){
        if(priority <0) {
            throw new IllegalArgumentException();
        }
        this.priority = priority;
    }

    public int getPriority() {
        return priority;
    }

    @Override
    public int compareTo(@NonNull PriorityRunnable another) {
        int me = this.priority;
        int anotherPri=another.getPriority();
        return me == anotherPri ? 0 : me < anotherPri ? 1 : -1;
    }

    @Override
    public void run() {
            doSomeThing();
    }

    protected abstract void doSomeThing();
}

利用抽象类继承Comparable接口重写其中的compareTo方法来比较优先级。

使用:

  mMyPriorityTheadPool.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for(int i = 0;i<30;i++){
                    final int priority = i;
                    priorityThreadPool.execute(new PriorityRunnable(priority) {
                        @Override
                        protected void doSomeThing() {
                            Log.d("MainActivity", "优先级为 "+priority+"  的任务被执行");
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });

                }
            }
        });

结果:前三个任务被创建的三个核心线程执行,之后的27个任务进入队列并且调用compareTo方法进行排序,之后打印出来的是经过排序后从大到小的顺序。

三:JAVA中的阻塞队列

由于上面的构造方法涉及到了阻塞队列,所以补充一些阻塞队列的知识。
阻塞队列:我的理解是,生产者——消费者,生产者往队列里放元素,消费者取,如果队列里没有元素,消费者线程取则阻塞,如果队列里元素满了,则生产者线程阻塞。

常见的阻塞队列有下列7种:

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的*阻塞队列。
DelayQueue:一个使用优先级队列实现的*阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的*阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

具体情况具体分析,选择合适的队列。
 

四:各个线程池总结及适用场景

newCachedThreadPool:

底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool:

底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(*的阻塞队列)
适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(*的阻塞队列)
适用:一个任务一个任务执行的场景

NewScheduledThreadPool:

底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景

五:线程池其它方法:

1.shutDown()  关闭线程池,不影响已经提交的任务

2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程

3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收

4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值。

5.beforeExecute() - 任务执行前执行的方法

6.afterExecute() -任务执行结束后执行的方法

7.terminated() -线程池关闭后执行的方法

参考出处链接:

Android性能优化之使用线程池处理异步任务
Android开发之线程池使用总结
线程池的种类,区别和使用场景


 =====

一、什么是线程池

顾名思义,线程池就是一个可以同时容纳多个线程执行的容器。在多线程编程中,我们不可避免地要用到线程池技术,那么我们为什么要使用线程池呢?这就要说说线程池的优点了:

  • 通过重用线程池中的线程,可以避免因重复创建和销毁线程带来的性能开销;
  • 有效控制线程的最大并发数,避免大量的线程因互相抢占资源而造成系统的阻塞;
  • 能够对线程进行简单的管理,提供定时执行、间隔循环执行等功能。

二、线程池的实现

Android中线程池真正的实现是ThreadPoolExecutor类,它的构造方法提供了一系列参数来配置线程池,我们通过它的构造方法来了解各个参数的含义。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize
    线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于空闲状态。但是当ThreadPoolExecutor的allowCoreThreadTimeOut属性值为true,那么空闲的核心线程也会有超时策略,这个时长由keepAliveTime决定,当等待超过keepAliveTime指定的时长,核心线程被终止。
  • maximumPoolSize
    线程池能容纳的最大线程数,当线程数达到这个数值,后续的任务会被阻塞,直到有其他线程被释放。
  • keepAliveTime
    非核心线程的闲置超时时长,超过这个时长非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性值为true,keepAliveTime同样会作用于核心线程。
  • unit
    设置keepAliveTime参数的单位。
  • workQueue
    线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
  • threadFactory
    线程工厂,专门为线程池生成新线程。

ThreadPoolExecutor执行任务时需要遵循如下规则:

  1. 如果线程池中的线程数量未达到核心线程数,那么会直接启动一个核心线程来执行新的任务;
  2. 如果线程池中的线程数量已经达到或超过核心线程数,那么任务会被插入到任务队列中排队等待;
  3. 如果线程池中的线程数量已经达到或超过核心线程数,并且任务队列已满,这个时候如果线程池中的线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务;
  4. 如果步骤3中线程数量达到线程池规定的最大值,那么就拒绝执行此任务,会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者任务执行请求被拒绝。
处理任务优先级排序:启动核心线程 -> 进入任务队列 -> 启动非核心线程 。

我们接下来从源码的角度分析一下任务执行的规则:

public void execute(Runnable command) {
   //传进来的线程为null,则抛出空指针异常
   if (command == null)
       throw new NullPointerException();
  
   //获取当前线程池的状态+线程个数变量
   int c = ctl.get();
   /**
    * 3个步骤
    */
   //1.判断当前线程池线程个数是否小于corePoolSize,小于则调用addWorker方法创建新线程运行,且传进来的Runnable当做第一个任务执行。
   //如果调用addWorker方法返回false,则直接返回
   if (workerCountOf(c) < corePoolSize) {
       if (addWorker(command, true))
           return;
       c = ctl.get();
   }

   //2.如果线程池处于RUNNING状态,则添加任务到阻塞队列
   if (isRunning(c) && workQueue.offer(command)) {

       //二次检查
       int recheck = ctl.get();
       //如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
       if (! isRunning(recheck) && remove(command))
           reject(command);

       //否者如果当前线程池线程空,则添加一个线程
       else if (workerCountOf(recheck) == 0)
           addWorker(null, false);
   }
   //3.新增线程,新增失败则执行拒绝策略
   else if (!addWorker(command, false))
       reject(command);
}

这样直接看代码可能还是不太清晰,所以我们直接看图会比较容易理解具体过程。

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
图片来源于网络

三、线程池的分类

第二节中对ThreadPoolExecutor的主要配置参数进行了详细的介绍,本节将介绍Android中常见的几种线程池,看看它们是如何通过配置ThreadPoolExecutor来实现自己的功能特性。

(1)FixedThreadPool

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

总结:核心线程数=最大线程数,即线程池中只有核心线程,这意味着所有线程不会被回收,除非线程池被关闭了。当所有线程都处于活动状态时,由于任务队列没有限制大小,因此新任务总是会进入任务队列排队等待,直到有线程空闲出来。由于FixedThreadPool只有核心线程并且这些线程不会被回收,因此它能够更加快速地响应外界的请求

(2)SingleThreadExecutor

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

总结:核心线程数=最大线程数=1,即线程池中只能有一个核心线程。SingleThreadExecutor可以确保所有任务都在同一线程中顺序执行,这使得在这些任务之间不需要处理线程同步问题

(3)CachedThreadPool

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

总结:核心线程数=0,即它只有非核心线程,并且线程池可以容纳的非核心线程数量为Integer.MAX_VALUE。线程池中的空闲线程都有超时机制,如果在60秒内没有执行任务,线程将会被系统回收。假如某个时刻所有线程都处于活动状态,那么就会创建新线程来执行新任务,否则直接使用空闲线程处理任务。CachedThreadPool的任务队列实际上是一个无法存储任务的队列,这将导致任何任务都会立即被执行。CachedThreadPool适用于执行大量耗时较少的任务。当所有线程都处于空闲状态时,它们会因为超时而全部被回收,这个时候CachedThreadPool中没有任何线程,这几乎不占用任何系统资源。

(4)ScheduledThreadPool

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

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
//延迟1秒执行,每隔1秒向线程池提交一次任务
Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1, 1, TimeUnit.SECONDS);

总结:核心线程数固定,非核心线程数为Integer.MAX_VALUE。非核心线程处于空闲状态超过一定时长就会被回收。ScheduledThreadPool主要用于执行定时任务以及有固定周期的重复任务

除了系统提供的这几类线程池之外,我们也可以根据实际场景灵活地配置线程池。


参考

《Android开发艺术探索》
java多线程系列:ThreadPoolExecutor源码分析

===

Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,主要由下列4个组件构成。

    ·corePool:核心线程池的大小。

    ·maximumPool:最大线程池的大小。

    ·BlockingQueue:用来暂时保存任务的工作队列。

    ·RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。

通过Executor框架的工具类Executors,可以创建3种类型的ThreadPoolExecutor。

    ·FixedThreadPool。

    ·SingleThreadExecutor。

    ·CachedThreadPool。 

下面将分别介绍这3种ThreadPoolExecutor。

FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实现。

public static ExecutorService newFixedThreadPool(int nThreads) { 

    return new ThreadPoolExecutor(nThreads,nThreads,

    0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

}

FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。

FixedThreadPool的execute()方法的运行示意图如图所示。

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
FixedThreadPool的execute()的运行示意图

上图的说明如下。

    1)如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。

    2)在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入 LinkedBlockingQueue。

    3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

FixedThreadPool使用*队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。使用*队列作为工作队列会对线程池带来如下影响。

    1)当线程池中的线程数达到corePoolSize后,新任务将在*队列中等待,因此线程池中的线程数不会超过       corePoolSize。

    2)由于1,使用*队列时maximumPoolSize将是一个无效参数。 

    3)由于1和2,使用*队列时keepAliveTime将是一个无效参数。

    4)由于使用*队列,运行中的FixedThreadPool(未执行方法shutdown()或 shutdownNow())不会拒绝任务   (不会调用RejectedExecutionHandler.rejectedExecution方法)。

SingleThreadExecutor详解

SingleThreadExecutor是使用单个worker线程的Executor。下面是SingleThreadExecutor的源代码实现。

public static ExecutorService newSingleThreadExecutor() { 

    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS    ,new LinkedBlockingQueue()));

}

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与 FixedThreadPool相同。SingleThreadExecutor使用*队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用*队列作为工作队列对线程池带来的影响与FixedThreadPool相同,这里就不赘述了。

SingleThreadExecutor的运行示意图如图所示。

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
SingleThreadExecutor的execute()的运行示意

对上图的说明如下。

    1)如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新线程来执行任务。

    2)在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入Linked- BlockingQueue。

    3)线程执行完1中的任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。

CachedThreadPool详解

CachedThreadPool是一个会根据需要创建新线程的线程池。下面是创建CachedThreadPool的源代码。

public static ExecutorService newCachedThreadPool() {     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

    60L, TimeUnit.SECONDS,new SynchronousQueue());

}

CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为 Integer.MAX_VALUE,即maximumPool是*的。这里把keepAliveTime设置为60L,意味着 CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。

FixedThreadPool和SingleThreadExecutor使用*队列LinkedBlockingQueue作为线程池的工作队列。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但 CachedThreadPool的maximumPool是*的。这意味着,如果主线程提交任务的速度高于 maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。

CachedThreadPool的execute()方法的执行示意图如图所示。

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:
 

对上图的说明如下。

    1)首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行       SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线       程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤

    2)当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执       行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1)将失败。       此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。

    3)在步骤2)中新创建的线程将任务执行完后,会执行 SynchronousQueue.poll(keepAliveTime,       TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60       秒钟内主线程提交了一个新任务(主线程执行步骤1)),那么这个空闲线程将执行主线程提交的新任务;否       则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不        会使用任何资源。

前面提到过,SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主线程提交的任务传递给空闲线程执行。CachedThreadPool中任务传递的示意图如图所示。

 
多线程之线程池基本内容
Android 多线程:线程池理解和使用总结
Android 线程池原理及使用
一:使用线程池的原因
二:几种常见的线程池
三:JAVA中的阻塞队列
四:各个线程池总结及适用场景
五:线程池其它方法:


作者:CodeKing2017
链接:https://www.jianshu.com/p/e9b0378db56a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。