线程池(Thread Pool)是一种基于池化思想管理线程的工具。使用线程池可以带来诸多好处:
①降低资源消耗:通过池化技术复用已创建的线程,减少线程创建和销毁的损耗。
②提高响应速度:任务到达时,特定情况下无需再创建线程。
③便于管理。
java中线程池相关的接口和类主要包括:
Executor是线程执行器
Runnable接口定义任务逻辑,通过这两个接口将任务的定义和执行分离开来。
ThreadPoolExecutor实现ExecutorService,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等服务
Executors中提供了一些默认提供的线程池,都是通过参数设置来实现不同的线程池机制
核心构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize: 核心线程数
maximumPoolSize:最大线程数
keepAliveTime:核心线程除外的空闲线程存活的最大时间,之后归还给OS
unit:keepAliveTime的单位
workQueue:阻塞任务队列
threadFactory:新建线程工厂
handler:拒绝策略,jdk默认提供四种,但是生产环境一般需要自定义实现
下面通过任务调度execute方法研究下参数间的关系
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//worker数量(理解为线程池中线程数量)比核心线程数corePoolSize少时,直接创建worker执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//此时worker数量>核心线程数corePoolSize
//将任务加到阻塞队列中,由线程池去调度
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//此时如果线程池依旧是运行状态,那么就是任务队列已满
//尝试新加非核心worker去执行任务
//执行任务失败或可能是非核心worker大于maximumPoolSize,则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
总结一下:
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
当线程池任务队列已满并且线程数量达到maximumPoolSize,线程池执行拒绝策略。
拒绝策略接口:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
jdk提供的四种策略介绍:
CallerRunsPolicy:由提交任务的线程取处理。这种情况是需要让所有任务都执行完毕。
AbortPolicy: 直接丢弃任务并抛出java.util.concurrent.RejectedExecutionException异常
DiscardPolicy:丢弃任务但不抛出异常
DiscardOldPolicy:丢弃任务最前面的异常,并重新提交被拒绝的任务。
一般生产环境都需要实现自定义的拒绝策略,将任务保存起来。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i=0; i<5; i++) {
final int j = i;
service.execute(()->{
System.out.println(j + " " + Thread.currentThread().getName());
});
}
service.shutdown();
}
}
SingleThreadExecutor创建的是一个核心线程数和最大线程数都是1,任务队列是LinkedBlockingQueue的无界队列 的线程池。单线程的线程池可以保证队列中的任务按照指定顺序执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
public static void main(String[] args) {
// 获取计算机有几个核
int processors = Runtime.getRuntime().availableProcessors();
// 固定个数的线程池,可以为每个CPU核绑定一定数量的线程数
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(processors * 5);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
fixedThreadPool.shutdown();
}
}
newFixedThreadPool创建的是一个固定线程数(核心线程数和最大线程数都是同一个指定值),任务队列是LinkedBlockingQueue的无界队列的线程池。线程池中最多有nThreads个线程处于活动状态,当线程中所有线程都处于活动状态时,新任务会加入无界队列等待执行,任务永远不会被拒绝,不会创建核心线程数意外的线程,线程也不会被销毁,适合平稳且固定的任务开发场景。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CachedPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
System.out.println(service);
for (int i = 0; i < 2; i++) {
service.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
service.shutdown();
}
}
CachedThreadPool创建的是一个核心线程数为0,最大线程数为最大整型值,任务队列是SynchronousQueue的线程池。SynchronousQueue队列的特点是他的take方法需要put方法等待,put方法需要需要taken方法等待,否则提交任务线程就会一直阻塞。队列是没有存储空间的,需要找到一个空闲线程去处理任务,如果当前没有空闲线程的话就新建线程处理,线程空闲超过60s就会被回收杀死。适合任务执行时间短,任务数量不明确的场景。他的缺点是如果提交任务数量远大于处理速度,就会不断创建线程耗尽系统资源。
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledPool {
public static void main(String[] args) {
//指定核心线程数
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.scheduleAtFixedRate(()->{
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}, 0, 500, TimeUnit.MILLISECONDS);
}
}
ScheduledThreadPool是创建一个指定核心线程数,最大线程数为最大整型值,任务队列是DelayedWorkQueue的线程池。