线程池使用与原理解析

1.为什么要使用线程池

在java编程中,对象的创建和销毁是很耗费资源的,创建一个对象要获取内存资源,为对未被引用的对象进行销毁的工作,虚拟机的垃圾回收机制将试图跟踪每一个对象。因此提高程序运行效率的途径之一就是尽可能地减少对象创建和销毁的次数,特别是针对一些很耗资源的对象。如何尽可能重用当前已存在的对象来持续输出服务,就是一些”池化资源”技术产生的原因。Android中许多常见的组件都都使用了”池化”的技术,如各种图片加载库,网络请求库。即使Android的消息传递机制中的Meaasge在使用Meaasge.obtain()时,就是使用的Meaasge池中的对象,因此这个概念很重要。本文将介绍的线程池技术同样符合这一思想。同时通过继承Thread类和实现runnalbe接口来实现多线程,缺乏统一的管理,多线程之间的竞争导致线程阻塞。
使用线程池的优势在于:

  • 重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
  • 能有效地控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
  • 能够简化多线程的管理,使线程的使用简单、高效;

    2.什么是线程池

    假设一个线程完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
    如果:T1 + T3 远大于T2,则可以采用线程池,以提高服务性能。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务程序性能的。
    java中的线程池是通过Executor框架来实现的。其中ThreadPoolExecutor是线程池的具体实现类,一般使用的各种线程池都是基于这个类实现的。其构造方法如下:
1
2
3
4
5
6
7
8
9
10
public (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...

}

其7个参数的含义如下:
corePoolSize:线程池中核心线程的数量,默认情况下核心线程一直存活即使在空闲状态下;
maximumPoolSize:线程池中允许的最大线程数;
keepAliveTime: 线程最大空闲的时间,当空闲线程空闲时间超过该值则回收该线程;
unit: keepAliveTime的时间单位是一个枚举类型;
workQueue:存放任务的BlockingQueue 队列;

阻塞队列(BlockingQueue)是用来控制线程同步的工具。如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。

threadFactory: 新建线程工厂,为线程池创建线程;
defaultHandler: 线程池的拒绝策略;

当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。

重点讲解:
其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.在当前的任务数超过maxmumPoolSize+workQueue之和时,即当前线程数目超过maximumPoolSize,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

3.线程池的种类及使用场景

可以通过配置线程池的参数,自定义线程池的工作方式,同时java已经根据常用应用场景配置了4中线程池,包括可缓存线程池(CachedThreadPool),定长线程池(FixedThreadPool),定时线程池(ScheduledThreadPool )
单线程化线程池(SingleThreadExecutor)。下面对此进行介绍。

自定义线程池的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
// 创建时,通过配置线程池的参数,从而实现自己所需的线程池
Executor threadPool = new ThreadPoolExecutor(
大专栏  线程池使用与原理解析 CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);

// 2. 向线程池提交任务:execute()
// 传入 Runnable对象
threadPool.execute(new Runnable() {

public void run() {
... // 线程执行任务
}
});

// 3. 关闭线程池shutdown()
threadPool.shutdown();

// 关闭线程的原理
// a. 遍历线程池中的所有工作线程
// b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

// 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
// 二者区别:
// shutdown:设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
// shutdownNow:设置线程池的状态 为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
// 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

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 ()无解阻塞队列。
  • 通俗:只有核心线程并且不会被回收、线程数量固定、任务队列无大小限制。如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中等待(无界的阻塞队列)。
  • 适用:控制线程最大并发数,执行长期的任务,性能好很多。

newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue () 无界阻塞队列
  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列),保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题。
  • 适用:一个任务一个任务执行的场景,不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等

NewScheduledThreadPool:

  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:定时或周期性执行任务的场景
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

// 4. 关闭线程池
scheduledThreadPool.shutdown();

未完待续。。。

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12408280.html