线程池解析
JVM内存分布.jpg创建线程需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间,销毁线程也需要回收这些内存,因此频繁创建和销毁线程将大量消耗系统资源,解决方法就是利用线程池,重用线程。使用线程池有利于控制最大并发数,可以实现任务队列的缓存和拒绝策略,实现定时和周期执行任务,可以更好地隔离不同的场景,避免因为大量任务堆积出现OOM。
使用线程池时强制规定:
1、创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
2、线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
3、线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这 样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
java.util.concurrent.ThreadPoolExecutor 的构造函数核心参数:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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 表示线程池中的线程空闲时间,线程在销毁前等待新任务的最大时限;
unit 表示 keepAliveTime 的单位;
workQueue 存放执行前的任务。只会存放通过 execute 函数提交的 Runnable 任务;
threadFactory 创建新线程的工厂;
handler 线程超限且队列容量也达到最大值时执行受阻的拒绝策略。
ThreadPoolExecutor中提供了四个公开的内部静态类用于拒绝策略:
AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy: 丢弃任务但是不抛出异常,不推荐。
DiscardOldestPolicy: 丢弃队列中等待最久的任务,然后把当前任务加入队列中。
CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
JAVA线程池执行流程
java线程池执行流程.png1、线程池初始创建时,并没有线程,任务队列是作为参数传递进来。
2、调用execute()方法执行任务时,会走上图流程判断:
2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
2.3 如果任务队列满了,且正在运行的线程数量小于 maximumPoolSize,那么创建非核心线程立刻运行这个任务;
2.4 如果任务队列满了,且正在运行的线程数量大于或等于 maximumPoolSize,那么执行拒绝策略。
3、当一个线程完成任务时,会从队列中取下一个任务来执行。
4、当一个线程没有任务需要执行,超过(keepAliveTime)时,线程池会判断,如果当前运
行的线程数大于 corePoolSize,那么这个线程就被停掉。线程池的所有任务完成后,它
最终会收缩到 corePoolSize 的大小。
@Async 配合线程池的使用
1、编写线程池配置类
@Configuration
@EnableAsync
public class TestThreadPoolConfig {
private static final int CORE_POOL_SIZE = 10;
private static final int MAX_POOL_SIZE = 20;
private static final int QUEUE_CAPACITY = 200;
private String threadNamePrefix = "test_thread_";
@Bean
public Executor testExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
// 设置队列容量
executor.setQueueCapacity(QUEUE_CAPACITY);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix(threadNamePrefix);
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
2、添加方法注解
在需要使用多线程处理的方法上添加@Async 注解,注解中可以指定自己创建的线程池:testExecutor
@Async("testExecutor")
public void handleTask() {
System.out.println("任务处理完成!!!");
}
注意:当此方法被当前类的另一个方法执行的时候,会导致单线程执行,只有在另一个勒种调用该类的这个异步方法时才是多线程。因为这个方法循环调用的,当在其他类中调用这个方法,每一次的循环都会创建这个对象,所以,每一次的创建和调用都是在一个独立的线程中进行的;如果是在当前对象中调用这个方法,那么在调用这个方法之前这个对象已经被创建了。
网友评论