美文网首页JAVA学习专栏
JAVA 线程池解析及配合@Async使用

JAVA 线程池解析及配合@Async使用

作者: 蒙嘉 | 来源:发表于2020-12-17 18:46 被阅读0次

线程池解析

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线程池执行流程.png

1、线程池初始创建时,并没有线程,任务队列是作为参数传递进来。
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("任务处理完成!!!");
}

注意:当此方法被当前类的另一个方法执行的时候,会导致单线程执行,只有在另一个勒种调用该类的这个异步方法时才是多线程。因为这个方法循环调用的,当在其他类中调用这个方法,每一次的循环都会创建这个对象,所以,每一次的创建和调用都是在一个独立的线程中进行的;如果是在当前对象中调用这个方法,那么在调用这个方法之前这个对象已经被创建了。

相关文章

网友评论

    本文标题:JAVA 线程池解析及配合@Async使用

    本文链接:https://www.haomeiwen.com/subject/sxfzgktx.html