Android 线程池使用总结

1,为什么使用线程池

在Java中,当我们想实现多线程程序的时候,通常会使用new 一个Thread来创建线程处理任务。如 :

new Thread(new Runnable() {
            @Override
            public void run() {
                //do sth .
            }
}).start();

这样会让程序创建一个新的线程并启动, run( )方法执行完之后,GC回收器会自动回收该线程。

在我们的程序中如果只需要1个2个子线程还好,但是假如你的程序需要大量的开启线程来处理任务,再向上面的方法做的话,就会导致系统的性能表现的非常糟糕。

在系统中创建大量的线程主要带来的影响如下 :

  1. 线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
  2. 大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
  3. 大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿

面对上述问题,我们的解决办法是 : 重用已有的线程,从而减少线程的创建。

这里我们就应该使用 线程池(ExecutorService),线程池的作用是进行线程的复用

2,线程池介绍

使用线程池的优点 :

  1. 线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
  2. 线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量。
  3. 在执行大量异步任务时提高了性能
  4. Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

Java为我们提供了ExecutorService线程池来优化和管理线程的使用

ExecutorService 是一个接口,它提供了众多的接口API来控制线程池中的线程。

ThreadPoolExecutor ,它实现了ExecutorService接口,并封装了一系列的api使得它具有线程池的特性,其中包括工作队列,核心线程数,最大线程数等等…

3,如何使用线程池

如果想使用线程池,可以直接new 一个 ThreadPoolExecutor,就可以创建一个线程池,ThreadPoolExecutor的构造方法如下:

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

其中的参数代表的含义分别为 :

  • corePoolSize : 线程池中的核心线程数量.
  • maximumPoolSize : 线程池中最大的线程数量
  • keepAliveTime : 当线程池中的线程数量超过了corePoolSize的时候,多余的空闲线程的保持活动时间,加入指定此参数为10S,那么如果多余的空闲线程在10S内还没有任务要处理的话,就会被销毁.
  • unit : 表示keepAliveTime的单位,枚举类型,如: TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)
  • workQueue : 任务队列,主要存储已经提交但未被执行的任务,不同的线程池采用的排队策略不一样.
  • threadFactory : 线程工厂,用来创建线程池中的线程,通常用默认的即可.
  • handler : 通常叫做拒绝策略,1、在线程池已经关闭的情况下 2、任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务 。在上面两种情况下,只要满足其中一种时,在使用execute()来提交新的任务时将会拒绝,而默认的拒绝策略是抛一个RejectedExecutionException异常。
    可以看到,如果通过new 一个 ThreadPoolExecutor来创建线程池,需要配置许多参数,略显麻烦。
    所以,官方推荐的是使用Executors的工厂方法来创建线程池,Executors类是一个官方提供的工厂类,其中封装了众多功能不一的线程池,使得创建线程池变得非常方便。
    主要提供了一下5种线程池 :
  • newFixedThreadPool() :
  • 返回一个固定线程数量的线程池,线程池种的线程数量不变。
  • 即使来了新任务也不会创建新的线程。
  • 已经创建好的线程不会被销毁。
    该线程池自始自终都是固定的几个线程在工作,因此该线程池可以控制线程的最大并发数。
  • newCachedThreadPool() :
  • 该方法返回一个可以根据实际情况调整线程池中线程数量的线程池。
    假如该线程池中的所有线程都在工作,此时有新的任务进来需要处理,该线程池就会创建新的线程去处理该任务。
    若有空闲的线程,就会调度该空闲线程去处理任务,而不会创建新的线程
  • 该线程池的默认 保持活动时间 为60S.
  • newSingleThreadExecutor() :
  • 该方法返回一个只有一个线程的线程池,所以每次只能执行一个线程任务。
  • 多余的任务会保存在任务队列中
  • 当这个唯一的线程空闲了才会按FIFO顺序方式去处理任务队列中的任务。
  • newScheduleThreadPool () :
  • 该方法返回一个可以控制线程内线程定时或周期性执行某任务的线程池。
  • newSingleThreadScheduledExecutor() :
  • 该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为1,而上面的可以指定线程池的大小。

创建这5种线程池的代码 :

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();

创建完线程池,通过.execute(Runnable runnable)方法执行。

例如 :

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
		for (int i=0;i<10;i++) {
			final int index = i;
			singleThreadPool.execute(new Runnable() {

				@Override
				public void run() {
					String threadName = Thread.currentThread().getName();
					System.out.println("线程:"+threadName+"正在执行第"+(index+1)+"个任务.");
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
			});
        }

我们创建了一个只含一个线程的线程池,然后让它执行10个任务,输出 :

线程:pool-1-thread-1正在执行第1个任务.
线程:pool-1-thread-1正在执行第2个任务.
线程:pool-1-thread-1正在执行第3个任务.
线程:pool-1-thread-1正在执行第4个任务.
线程:pool-1-thread-1正在执行第5个任务.
线程:pool-1-thread-1正在执行第6个任务.
线程:pool-1-thread-1正在执行第7个任务.
线程:pool-1-thread-1正在执行第8个任务.
线程:pool-1-thread-1正在执行第9个任务.
线程:pool-1-thread-1正在执行第10个任务.