当程序中线程创建过多或频繁创建/删除线程,需要消耗服务器大量资源,且易造成OOM,所以人们,创建了线程池来管理线程的创建和销毁.
1 常见的四种创建线程池方法
使用Executors类提供的方法,很容易获取线程池.
tips: alibaba编码规范手册上不推荐使用该方法,建议使用ThreadPoolExecutor方法,下章讲.
1 newFixedThreadPool(固定大小的线程池)
@Test
public void newFixedThreadPoolTest() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(new Date() +Thread.currentThread().getName());
//线程睡眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//线程睡眠 防止线程执行前就结束
Thread.sleep(3000);
executorService.shutdown();
}
运行结果:
Mon Mar 22 20:37:57 CST 2021pool-1-thread-1
Mon Mar 22 20:37:57 CST 2021pool-1-thread-2
Mon Mar 22 20:37:58 CST 2021pool-1-thread-1
说明: 创建一个线程数量为2的线程池,使用for循环提交了三个任务,每个任务睡眠一秒,前面只有两个任务拿到线程执行,后面等下一轮线程执行.且执行的时间差为1秒.
2 newCachedThreadPool(可缓存的线程池)
@Test
public void newCachedThreadPoolTest() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(new Date() +Thread.currentThread().getName());
//线程睡眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//线程睡眠 防止线程执行前就结束
Thread.sleep(1000);
executorService.shutdown();
}
运行结果:
Mon Mar 22 22:06:06 CST 2021pool-1-thread-2
Mon Mar 22 22:06:06 CST 2021pool-1-thread-1
Mon Mar 22 22:06:06 CST 2021pool-1-thread-3
**说明: **创建一个可以缓存的线程池,对线程数没有限制,可创建JVM能允许的最大线程数量.
3 newSingleThreadExecutor(单线程的线程池)
@Test
public void newSingleThreadExecutorTest() throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(new Date() +Thread.currentThread().getName());
//线程睡眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//线程睡眠 防止线程执行前就结束
Thread.sleep(4000);
executorService.shutdown();
}
运行结果:
Mon Mar 22 22:11:00 CST 2021pool-1-thread-1
Mon Mar 22 22:11:01 CST 2021pool-1-thread-1
Mon Mar 22 22:11:02 CST 2021pool-1-thread-1
说明: 单个线程执行所有任务,类似单线程执行.
4 newScheduledThreadPool(定时及周期性任务的线程池)
==定时执行==
@Test
public void newScheduledThreadPoolTest() throws InterruptedException {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
System.out.println("开始时间"+new Date());
executorService.schedule(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程执行时间"+new Date() + Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 1, TimeUnit.SECONDS);
//线程睡眠 防止线程执行前就结束
Thread.sleep(3000);
executorService.shutdown();
}
运行结果:
开始时间Mon Mar 22 22:24:18 CST 2021
线程执行时间Mon Mar 22 22:24:19 CST 2021pool-1-thread-1
说明: 创建一个固定大小为2的线程池,设置延迟1秒执行任务.
==周期执行==
@Test
public void newScheduledThreadPoolTest() throws InterruptedException {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
System.out.println("开始时间"+new Date());
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程执行时间"+new Date() + Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 1,3, TimeUnit.SECONDS);
//线程睡眠 防止线程执行前就结束
Thread.sleep(8000);
executorService.shutdown();
}
运行结果:
开始时间Mon Mar 22 22:28:23 CST 2021
线程执行时间Mon Mar 22 22:28:24 CST 2021pool-1-thread-1
线程执行时间Mon Mar 22 22:28:27 CST 2021pool-1-thread-1
线程执行时间Mon Mar 22 22:28:30 CST 2021pool-1-thread-2
说明: 创建一个线程数为3的周期线程池,设置每个一秒执行一次任务,总共执行三次.
总结:
| 线程池类型 | 线程类型 | 数量 | 特点 | 应用 |
|---|---|---|---|---|
| newFixedThreadPool | 核心 | 固定 | 核心线程处于空闲状态,不会被回收 | 控制线程最大并发数 |
| newCachedThreadPool | 核心&非核心 | 核心固定/非核心不固定 | 非核心线程闲置时,会被立即回收 | 执行定时/周期任务 |
| newSingleThreadExecutor | 非核心 | 不固定 | 优先使用闲置线程处理任务 / 无线程使用,则新建线程 / 灵活回收空置线程 | 执行量多,耗时少的任务 |
| newScheduledThreadPool | 核心 | 1 | 所有任务按照指定顺序在一个线程中执行 | 单线程 |
-
FixedThreadPool和SingleThreadExecutor线程池中,使用的是LinkedBlockingQueue阻塞队列,请求处理堆积,可能会耗费很多内存,甚至OOM. -
CachedThreadPool和ScheduledThreadPool线程池,主要是线程最大数量是Integer.MAX_VALUE,可能创建巨量的线程,导致OOM.
==综上等原因:== alibaba编程手册建议不使用上述方法创建线程池.而是使用ThreadPoolExecutor类创建线程池.
2 ThreadPoolExecutor类创建线程池
1 ThreadPoolExecutor常用构造方法
//构造1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//构造2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
//构造3
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//构造4
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| corePoolSize | 核心线程数 |
| maximumPoolSize | 线程池最大线程数 |
| workQueue | 任务队列 |
| keepAliveTime | 线程闲置超时时间 |
| TimeUnit | 指定keepAliveTime的时间单位 |
| threadFactory(可选) | 线程工厂 |
| handler(可选) | 拒绝策略 |
2 线程池参数说明
1 workQueue
任务队列: 基于阻塞队列实现的,采用生产者和消费者模式.
队列接口为BlockingQueue,常见阻塞实现如下:
-
ArrayBlockingQueue 由数组结构组成的有界阻塞队列
-
LinkedBlockingQueue 链表结构组成的有界阻塞队列,未指定容量,默认Integer最大值
-
PriorityBlockingQueue 支持优先级排序的无界阻塞队列
-
DelayQueue 无界优先级阻塞队列,通过执行时延从队列中提取任务
-
SynchronousQueue 不存储元素的阻塞队列
-
LinkedBlockingDeque 双向队列实现的有界双端阻塞队列
-
LinkedTransferQueue 无界的阻塞队列
tips:
-
有界: 当任务队列达到最大值且超过最大线程数,就会执行拒绝策略.
-
无界: 任务队列可以一直添加任务
2 threadFactory
线程工厂: 创建线程的方式.
需要实现ThreadFactory接口,且实现**newThread(Runnable r)**方法
Executors中默认的线程工厂如下:
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
3 handler
拒绝策略: 线程池的线程达到最大线程时,需要执行拒绝策略.
需要实现RejectedExecutionHandler接口,并实现**rejectedExecution(Runnable r, ThreadPoolExecutor executor)**方法.
Executors中提供的4种拒绝策略:
- AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
- CallerRunsPolicy:由调用线程处理该任务
- DiscardPolicy:丢弃任务,但是不抛出异常 (可以配合这种模式进行自定义的处理方式)
- DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries {@code execute}, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}