Hello朋友们,我是RatelBlog的李某人。
今天我们来聊一聊线程池,什么是线程池?
线程池其实就是一堆线程的集合,我们把这个集合称为线程池。在并发编程中使用线程池可以很好的提高服务
的性能,可以用来管理和维护线程以及复用空闲的线程,从而避免频繁的创建和销毁线程所消耗的系统资源。
先来了解一下线程池的5种状态
1.Running(运行状态):线程池可以接受新的任务,也可以处理阻塞队列中的任务。执行shutdown()方法可进入
待关闭状态(ShutDown),执行shutdownNow()方法可以进入停止状态(Stop)。
2.ShutDown(待关闭状态):线程池不再接受新的任务,继续处理阻塞队列中的任务。当队列中的任务为空时,
并且线程池中执行的任务为空会进入整理状态(Tidying)。
3.Stop(停止状态):线程池不再接受新的任务,也不处理阻塞队列中的任务。并会中止正在处理的任务,
当执行的任务为空会进入整理状态(Tidying)。
4.Tidying(整理状态):所有的任务都已结束,线程池会变成整理状态(Tidying)。并会调用终止方法
terminated()。terminated()方法在ThreadPoolExecutor类中是空的,没有任何实现需要自己重写。
终止方法执行完毕后会进入Terminated(终止状态)。
5.Terminated(终止状态):线程池完全终止,并完成了所有资源的释放。
在Java中可以通过Executors类来创建线程池,内部还是使用的ThreadPoolExecutor实现的。
根据阿里巴巴的开发者手册,不建议直接使用Executors来直接创建线程池,因为在创建
(缓存线程池)CachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而
导致OOM异常。在创建定长线程池(FixedThreadPool)和单线程化线程池(SingleThreadPool)时,允许
的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
所以在实际的工作中,还是建议直接通过ThreadPoolExecutor来创建线程池。
那么我们先来了解一下ThreadPoolExecutor参数说明。
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;
}
注意:
核心线程:就是闲置不会被销毁的线程,可以设置allowCoreThreadTimeout=true(默认false),超过闲置
时间会被销毁。
非核心线程:超过闲置时间,线程会被销毁。
参数 | 作用 |
| 核心线程数。当线程数小于核心线程数时,即使有空闲线程,线程池也会优先创建新线程处理。 注意: 线程池新建线程的时候,当前线程总数< corePoolSize,新建的线程即为核心线程。 |
| 最大线程数。表示线程池中最多能够创建的线程数量。 注意: 线程总数 = 核心线程数 + 非核心线程数 |
| 线程空闲时间。keepAliveTime即为空闲线程允许的最大的存活时间。如果一个非核心线程空闲状态的时长超过keepAliveTime了,就会被销毁掉。 注意:如果设置 |
| 空闲线程时间单位。 注意: NANOSECONDS:1微毫秒 = 1微秒 / 1000 MICROSECONDS:1微秒 = 1毫秒 / 1000 MILLISECONDS:1毫秒 = 1秒 /1000 SECONDS:秒 MINUTES:分 HOURS:小时 DAYS:天 |
| 保存任务的阻塞队列。 注意: 当核心线程都在工作的时候,新提交的任务就会被添加到这个阻塞队列中进行排队等待;如果阻塞队列也满了,线程池就新建非核心线程去执行任务。workQueue维护的是等待执行的Runnable对象。 |
| 创建线程的工厂。 |
| 任务拒绝处理器。 两种情况会拒绝处理任务: 1.当线程数已经达到maximumPoolSize,切队列已满,会拒绝新任务。 2.当线程池处于ShutDown|Stop拒绝接受新任务。 ThreadPoolExecutor默认有四个拒绝策略: 1、ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException 当然也可以实现RejectedExecutionHandler接口,可自定义处理器 。 |
线程池工作流程
ThreadPoolExecutor参数大概了解了,然后咱们了解下线程池的工作流程。
1.提交任务
2.核心线程数未达到corePoolSize,创建核心线程执行任务。
3.核心线程数达到corePoolSize,任务队列未满进入队列等待。
4.队列已满,总线程池未满创建非核心线程执行任务。
5.总线程已满,按照拒绝策略执行。
常用的 workQueue 类型
缓存线程池--CachedThreadPool
newCachedThreadPool将创建一个可缓存的线程池。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
阿里巴巴开发者手册:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程从而导致OOM.
Integer.MAX_VALUE
特点:
1.核心线程数为0,总线程大小为Integer.MAX_VALUE 2147483647,所以也可以看作是线程数无限制。
2.有空闲线程就复用,否则就新建线程处理任务。
3.默认的空闲时间为60秒。
OK 我们来模拟一个场景,首先拿到了一个批次的用户集合,给这些用户分发消息,那么就可以使用到线程
池并发去处理任务。
public static void main(String[] args) {
String[] users = {"张三","李四","王五","司总"};
ExecutorService executorService = Executors.newCachedThreadPool();
for (String user : users) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000L);
} catch (Exception e) {
e.printStackTrace();
}
logger.info(Thread.currentThread().getName()+"执行分发消息任务:"+user);
}
});
}
}
pool-1-thread-4执行分发消息任务:司总
pool-1-thread-2执行分发消息任务:李四
pool-1-thread-1执行分发消息任务:张三
pool-1-thread-3执行分发消息任务:王五
注意:
如果上面这个案例不使用线程池就单线程去执行,就需要耗时4秒。所以使用线程池可以大大的提高效率。
定长线程池--FixedThreadPool
newFixedThreadPool创建一个固定长度的线程池,每次提交一个任务的时候就会创建一个新的线程,直到
达到线程池的最大数量限制。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
阿里巴巴开发者手册:允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致OOM.
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
特点:
1.线程全部都是核心线程,需要传入初始线程大小数。
2.超出的线程会在队列中等待。
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 11; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
logger.info(Thread.currentThread().getName() + "~~~");
}
});
}
}
单线程线程池--SingleThreadExecutor
newSingleThreadExecutor创建一个单线程线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
阿里巴巴开发者手册:允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致OOM.
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
特点:
1.只有一个核心线程处理任务。
2.所有任务按照工作队列的排队顺序执行,先进先出的顺序。
3.单个线程的线程池就是线程池中只有一个线程负责任务,所以 corePoolSize 和 maximumPoolSize 的数值
都是为1;当这个线程出现任何异常后,线程池会自动创建一个线程,始终保持线程池中有且只有一个存活的
线程。
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 4; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
logger.info(Thread.currentThread().getName() + "~~~");
}
});
}
}
pool-1-thread-1~~~
pool-1-thread-1~~~
pool-1-thread-1~~~
pool-1-thread-1~~~
定时线程池--ScheduledThreadPool()
newScheduledThreadPool创建一个固定长度的线程池,并且以延迟或者定时的方式去执行任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
注意:每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
1.scheduleAtFixedRate:表示以固定频率执行的任务,如果当前任务耗时较多,超过定时周期period,
则当前任务结束后会立即执行。
第一个command参数是任务实例,
第二个initialDelay参数是初始化延迟时间,
第三个period参数是间隔时间,
第四个unit参数是时间单元。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
logger.info(Thread.currentThread().getName()+"定时任务:每秒执行一次");
}
},0,1, TimeUnit.SECONDS);
}
pool-1-thread-1定时任务:每秒执行一次
pool-1-thread-1定时任务:每秒执行一次
pool-1-thread-2定时任务:每秒执行一次
pool-1-thread-1定时任务:每秒执行一次
2.scheduleWithFixedDelay:表示以固定延时执行任务,延时是相对当前任务结束为起点计算开始时间。
第一个command参数是任务实例,
第二个initialDelay参数是初始换延迟时间,
第三个delay参数是延迟间隔时间,
第四个unit参数是时间单元
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
logger.info(Thread.currentThread().getName()+"延迟任务:上一次任务结束后,延迟2秒后执行");
}
},0,2, TimeUnit.SECONDS);
}
pool-1-thread-1延迟任务:上一次任务结束后,延迟2秒后执行
pool-1-thread-1延迟任务:上一次任务结束后,延迟2秒后执行
pool-1-thread-2延迟任务:上一次任务结束后,延迟2秒后执行
execute与submit区别
1.定义的位置不同
execute定义在Executor接口中,ExecutorService接口继承了Executor接口,AbstractExecutorService类
实现了ExecutorService接口,ThreadPoolExecutor继承了AbstractExecutorService抽象类。
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor
public abstract class AbstractExecutorService implements ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
submit是定义在ExecutorService接口中,AbstractExecutorService类实现了ExecutorService接口
public abstract class AbstractExecutorService implements ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
2.入参不同
只能接受Runnable
void execute(Runnable command);
submit方法被重载了三次,分别对用三个不同的参数
public Future<?> submit(Runnable task)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回null。
public <T> Future<T> submit(Runnable task, T result)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回
给定的结果。
public <T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
3.返回值
execute没有返回值
submit返回future,可以获取到线程返回的结果数据还可以返回异常。
public Future<?> submit(Runnable task)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回null。
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<?> future = executorService.submit(new Runnable() {
@Override
public void run() {
logger.info("线程池执行");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//future.get()会阻塞主线程
logger.info((String) future.get());
logger.info(Thread.currentThread().getName()+"主~~~~");
}
[pool-1-thread-1] INFO com.testing.rest_template_demo.ThreadPoolTest - 线程池执行
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - null
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~
其它方法:
//尝试取消此任务的执行 返回true取消成功 false取消失败
future.cancel(true)
//正常终止、例外或取消——在所有这些情况下,此方法都将返回true
future.isDone();
//如果此任务在完成前被取消,则返回true
future.isCancelled();
public <T> Future<T> submit(Runnable task, T result)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回
给定的结果。
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Runnable() {
@Override
public void run() {}
}, "结果");
logger.info((String) future.get());
logger.info(Thread.currentThread().getName()+"主~~~~");
}
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 结果
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new Runnable() {
@Override
public void run() {
int i = 1 / 0;
}
}, "结果");
try {
logger.info((String) future.get());
} catch (Exception e) {
logger.info("捕获到异常" + e.getMessage());
}
logger.info(Thread.currentThread().getName() + "主~~~~");
}
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 捕获到异常java.lang.ArithmeticException: / by zero
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~
public <T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(new MyCallable());
logger.info(future.get().toString());
logger.info(Thread.currentThread().getName() + "主~~~~");
}
static class MyCallable implements Callable {
@Override
public Object call() throws Exception {
return "我是Callable返回数据";
}
}
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 我是Callable返回数据
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~