对于普通新线程的使用,我们已经非常熟悉了,甚至从接触Android不久,我们就已经对开启一个新线程有所了解,毕竟语法非常简单,随手就可以开一条线程出来:
|
|
这种匿名内部类的写法因为足够简便所以很受欢迎,还有两种广为人知的线程开启方法,分别是继承Thread和实现Runnable接口,当然换汤不换药,原理上都是调用Thread对象的start方法开启新线程。如果只是需要开一两条新线程做一些轻量级的操作确实可以这么写,但是这样写有很多弊端:
- 首先很明显的是,这种开启新线程的写法没有数量的限制,也就是说如果有一个需要开启大量新线程的需求,则每次都会新开辟一块内存用来运行新线程,大量线程的创建和执行将直接影响系统的吞吐量,如果内存占用过多,甚至会导致oom;
- 其次,不只是线程的创建,线程的销毁也是需要时间的,当有大量线程创建和销毁时,性能上的损耗是很明显的;
- 同时,线程太多且缺乏统一管理的情况下,可能会出现大量线程抢占系统资源导致的阻塞现象;
- 缺乏定时执行、定期执行、单线程、并发控制等功能。
而将上面的弊端解决的,就是线程池:
- 重用存在的线程,减少对象创建、销毁的开销,提高性能;
- 能够控制最大线程数,避免大量线程出现抢占系统资源导致阻塞的情况,提高系统资源的利用率;
- 提供定时、定期、单线程、并发数控制等功能。
所以说,在不知道可能会需要创建多少新线程的地方,应该优先考虑使用线程池。下面就线程池进行一下详解。
整个线程池最顶部的接口是Executor,它提供一个execute方法,接收一个实现Runnable接口的对象作为参数,然而真正实现线程池的时候一般不是使用Executor来实现,而是使用另一个实现了Executor接口的接口ExecutorService。ExecutorService基本提供了线程池管理线程所需要的全部方法,所以ExecutorService才是真正用来实现线程池的接口。
而最终实现ExecutorService接口,将方法具体下来实现线程池功能的、真正意义的线程池类是ThreadPoolExecutor。
像其他实体类一样,ThreadPoolExecutor类的初始化也是要通过调用构造函数,我们来看一下ThreadPoolExecutor的构造函数:
|
|
打开源码的你可能有点懵逼,但是你没有看错,ThreadPoolExecutor类有四个构造函数,上面这个五个参数的构造函数是其中看起来最简单的、也就是参数最少的一个。
最简单的构造函数都是五个参数,看着就很头大,源代码开发者自然不会不照顾用户情绪,所以其实早已准备好一个工厂类Executors,通过这个工厂类可以十分快捷的创建四种比较常用的线程池,下面暂时把ThreadPoolExecutor放在一边,看一下这四种线程池。
四种常用的线程池
newCachedThreadPool
|
|
这是一个可缓存、且可根据实际情况调整线程数量的线程池。
使用这个线程池对象调用execute方法时,如果有以前创建的可用的线程,该线程就会被重用,如果没有可用的线程,就重新创建一个新线程添加到池中。
之所以要把可用的重点标注出来,是因为并不是所有的以前构建的线程都被缓存可用,newCachedThreadPool 只会将没有任务运行的线程缓存60s,超过60s没有被使用的线程就会被终止并从缓存中移除。
注意创建这种线程池的时候可以不传参或者传入一个ThreadFactory对象,也就是说不需要传入与线程数有关的参数,因为newCachedThreadPool默认最大线程数是Integer.MAX_VALUE,相当于没有对最大线程数做限制。
也就是说,这种线程池可以自动根据情况实现线程的复用,而且使用这种线程池不用担心线程的结束问题,如果缓存的线程超过60s没有被复用就会被终止移除。
这种线程池不适合面向连接的daemon型任务,但是是执行很多生存期很短的异步任务时的首选
newSingleThreadPool
|
|
根据类名也可以看出来,这个线程池只创建一个线程,实际上从源码上看,这个类的创建方式就是创建一个核心线程数和最大线程数都为1的ThreadPoolExecutor对象,所以也不需要在创建的时候传入线程数之类的参数。
这种线程池中因为只有一个线程在运行,所以自然所有任务都是串行的,除了正在运行的任务,其他任务按照提交顺序存储在一个队列中,之后会依次按照提交顺序处理任务。
需要注意的是,如果这个唯一的工作线程因为异常停止,那么会有一个新的线程来替代它。
newFixedThreadPool
|
|
这个线程池创建的时候要传入一个数字,这个数字即这个线程池固定的最大线程数大小,它也能复用以前创建的空闲的线程,但不能自由的创建新线程,如果在超过固定线程数后还传进新建线程的任务,只能放到队列中等待,直到当前满了的线程池中有线程终止移出池子,再按提交的顺序上去运行。
需要注意的是,这种线程池相比cache线程池更适合稳定固定、长时间连接的的正规并发编程,多用于服务器。
newScheduledThreadPool
|
|
这是一个调度型的线程池,之前提到的定时或定期执行任务的功能就是由这种线程池实现的。
newSingleThreadScheduledExecutor
|
|
这种线程池也可以控制定时或定期执行任务,但是与newScheduledThreadPool的差别是后者可以指定线程数,而这个线程池是固定的一条线程。
上面五种线程池是可以由工厂类Executors直接创建的,也是官方推荐的创建线程池的方法,但是点进对应的源代码后不难发现,它们本质上还是调用了ThreadPoolExecutor类来构造线程池对象。
例如 newFixedThreadPool:
|
|
所以在大概了解一下五种常用的线程池之后,对于每一种线程池的分析最后还是要深入到ThreadPoolExecutor中解释。
下一篇我们会从源码对线程池进行分析。