一、作用
①降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
②提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
③方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
④管理线程。线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
二、相关概念
1. 线程池相关配置
和线程池相关的类ThreadPoolExecutor
,该类的构造函数的配置。
(1)int corePoolSize
核心线程数。
新建线程时,若当前线程数 x [0,corePoolSize),新建的是核心线程;若[corePoolSize,+~),新建的是非核心线程。
核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干。
如果指定ThreadPoolExecutor
的allowCoreThreadTimeOut=true
,那么核心线程如果闲置超过一定时间(时长可由参数配置),就会被销毁掉。
(2)int maximumPoolSize
线程池中线程总数最大值(线程总数=核心线程数+非核心)
(3)long keepAliveTime
该线程池中非核心线程闲置超时时长。
若一个非核心线程闲置状态超过设定时长,就会被销毁掉。如果设置allowCoreThreadTimeOut=true
,该参数则会作用于核心线程。
(4)TimeUnit unit
keepAliveTime 的单位,TimeUnit
是一个枚举类型,包括:
NANOSECONDS:1微毫秒
MICROSECONDS:1微妙
MILLISECONDS:1毫秒
SECONDS:1秒
MINUTES:1分钟
HOURS:1小时
DAYS:1天
(5)BlockingQueue workQueue
线程池中的任务队列:维护者等待执行的 Runnable 对象。
所有的线程都在干活时,新添加的任务就会被添加到这个队列中等待出来,如果队列满了,则新建非核心线程执行任务。
- SynchronousQueue
这个队列接到任务时会直接提交给线程处理,如果此时线程池中的线程都在工作,那就新建一个线程来处理这个任务。
为了保证不会出现『线程数达到了 maximumPoolSize』的错误,使用这个类型队列时,maximumPoolSize=Integer.MAX_VALUE
。
-
LinkedBlockingQueue
收到任务时: -
ArrayBlockingQueue
当收到任务时,
若核心线程数<corePoolSize,则新建核心线程完成任务;
若当前线程数=corePoolSize,则将任务移入队列等待;
若队列已满,新建非核心线程执行任务;
若线程总数=maximumPoolSize,抛出异常。 -
DelayQueue
2. 线程池执行任务时遵循的规则
(1)如果线程池中线程数量未达到核心线程数量,那么直接启动一个核心线程来执行任务。
(2)如果线程池中线程数量已达到核心线程数量,那么任务会被插入到任务队列中排队等待执行。
(3)如果(2)中任务无法插入到队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
(4)如果(3)中线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。
3. 常见线程池
通过 Executors 可以创建四类线程池。
(1)FiexThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0 L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue < Runnable > ());
}
-
核心线程数
只有核心线程 -
线程池能容纳的最大线程数
-
线程闲置时的超时时间
这些核心线程没有超时机制。
该线程池只有核心线程,且这些核心线程不会被回收,意味着它能快速响应外界请求。 -
任务队列
任务队列没有大小限制
(2)CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60 L, TimeUnit.SECONDS,
new SynchronousQueue < Runnable > ());
}
-
核心线程数
没有核心线程 -
线程池能容纳的最大线程数
无限大 -
线程闲置时的超时时间
该线程池只有非核心线程,这些线程的超时时间是 60s。
当线程池中线程都处于活动状态时,线程池会创建新的线程处理任务,否则利用空闲线程来处理新任务。
- 任务队列
该任务队列无法插入任务。这将导致任务会立即被执行,该线程池适合执行大量的耗时较少的任务。
(3)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());
}
-
核心线程数
核心线程数固定,非核心线程数没有限制。 -
线程池能容纳的最大线程数
无限大 -
线程闲置时的超时时间
当非核心线程被闲置10毫秒后,会被立即回收。 -
任务队列
主要用于执行定时任务和有固定周期的重复任务
(4)SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0 L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue < Runnable > ()));
}
-
核心线程数
该线程池中只有一个核心线程。它确保了所有的任务都在一个线程中按顺序执行。这使得任务间不需要处理线程同步问题。 -
线程池能容纳的最大线程数
-
线程闲置时的超时时间
-
任务队列
无限大
三、使用
1. 通过 ThreadPoolExecutor 创建一个线程池
(1)创建线程
// 新建线程池
ExecutorService service = new ThreadPoolExecutor(5, 10, 10, TimeUnit.HOURS, new LinkedBlockingDeque < Runnable > ());
(2)提交任务
有execute()
和submit()
两种方法。
// 使用execute向线程池提交任务
service.execute(new Runnable() {
public void run() {
}
});
// 使用submit提交任务
Future < Integer > future = service.submit(new Callable < Integer > () {
@Override
public Integer call() throws Exception {
return 2;
}
});
try {
Integer num = future.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
(3)线程池关闭
// 关闭线程池
service.shutdown();
service.shutdownNow();
2. 直接使用现有的4种线程池类创建线程池
ExecutorService tp = Executors.newFixedThreadPool(4);
ExecutorService tp2 = Executors.newCachedThreadPool();
ExecutorService tp3 = Executors.newScheduledThreadPool(4);
ExecutorService tp4 = Executors.newSingleThreadExecutor();
网友评论