简单理解Java四种线程池
2019.03.09Android学习周记——java中的四种线程池并自己封装线程池
1. 线程(Thread)
线程是计算机调度的最小单位是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
以上概念能理解最好,当然没理解也没关系,暂时把一个线程看做一个任务。
2. 多线程
当我们没有接触线程前,我们的程序都是执行在主线程中的。当大家需要考虑到线程问题的时候一般是需要进行耗时操作的时候,比如网络请求等。如果把网络请求放在主线程中的话,Android会报错,单单是Java的话理论上是允许的,不过会导致主线程被迫等待这个任务结束完后才能继续,可能会对性能造成影响。
解决方案是:新开一个线程来完成这个任务,新开线程主要有三种方法,一种是继承Thread类,一种是实现Runnable接口,还有就是实现Callable接口。他们的优劣不是重点,不再赘述。
3. 线程池
既然已经有了解决方案,在需要开新线程的时候开一个就好了嘛,为何需要线程池呢?
因为如果一个任务就新开一个线程的话对系统的开销非常大,如果频繁地开新的任务但是每个任务又只需要很短的时间无疑会加重系统的负担,比如服务器回应用户的登录请求。Java封装的线程池为这种现象提供了解决方案,它主要通过一个等待队列的设置来实现,比如我的线程池大小为5,然而现在5个线程都在运行中,那么这时候再提交一个新的任务就会被放在等待队列,当任何一个线程中的任务执行完毕后再进行。
4. JDK-5 Executor 框架
在 Java 5 之后,并发编程引入了一堆新的启动、调度和管理线程的API。Executor 框架便是 Java 5 中引入的,其内部使用了线程池机制,它在 java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在 Java 5之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用 Executor 在构造器中。
Executor 框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable 等。
这里我们主要使用Executors来创建线程池
需要注意的是,我们可以将线程池中的线程看做一个对象。好比一根管子,然后把任务放到这个管子里执行。
4.1 newCachedThreadPool 缓存线程池
public static ExecutorService newCachedThreadPool()
顾名思义,这是一个具有缓存特性的线程池:
- 先检查这个线程池中有没有闲置(IDLE)的线程,如果有那就把新任务放入这个线程中进行,如果没有那就新开一个线程加入池中并执行新任务。
- 闲置的默认时间是60s,超过了这个时间如果没有新的任务使用这个线程,那么这个线程将被销毁。
4.2 newFixedThreadPool 核心线程池
public static ExecutorService newFixedThreadPool(int corePoolSize);
实际上核心线程池和缓存线程池在系统底层实际上是一样的,只是参数不一致,线程池中的线程数量固定,且IDLE为0;
- 和缓存线程池一样,优先使用闲置的线程,不过最多只能有corePoolSize数量的线程同时存在。
- 如果有线程池已满且有新任务加入,则进入缓冲队列
4.3 newScheduledThreadPool 调度线程池
public static ScheduledExecutorService new ScheduledThreadPool(int corePoolSize)
- 该线程池内的线程可以调度(推迟/周期)
4.4 newSingleThreadExecutor 单例线程池
public static ExecutorService newSingleThreadExecutor();
- 单例线程池,只含有一个线程
- 和缓存线程池同样的底层实现,也只是参数不同,线程数目是1,IDLE是0s
5. 总结
一般来说,CachedTheadPool 在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的 Executor 的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用 FixedThreadPool。
——《Thinking in Java》