ThreadPoolExecutor源码思考

ThreadPoolExecutor自定义线程工厂

ThreadPoolExecutor线程池采用ThreadFactory中默认的DefaultThreadFactory实现类来创建的,使用工厂方法模式来设计的线程工厂创建线程。(而工厂方法模式对于我的理解:一个产物对应一个工厂)

static class InnerThreadFactory implements ThreadFactory {
	@Override
	public Thread newThread(Runnable r) {
		Thread thread = new Thread(r);
		LocalDateTime ldt = LocalDateTime.now();
		DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyyMMd HH:mm:ss");
		thread.setName(THREAD_PREFIX + ldt.format(pattern));
		return thread;
	}
}

ThreadPoolExecutor任务饱和策略

  • AbortPolicy:直接抛出RejectedExecutionException异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。

线程池什么时候启动

在new ThreadPoolExecutor类时并没有启动线程池,只是设置了参数,而线程池的启动是在执行execute方法(源码中是addWorker方法)。

线程池生命周期

image

image

任务执行机制

任务机制1,任务调度

image

任务机制2,任务缓冲

image

任务机制3,任务申请

getTask方法:
image

任务机制4,任务拒绝

  • AbortPolicy:直接抛出RejectedExecutionException异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。

Worker线程管理机制

线程增加

addWorker方法:
image

线程回收

processWorkerExit方法:
image

线程执行任务

runWorker方法:
image

线程释放策略

  • 第一种策略:使用allowCoreThreadTimeOut方法对核心线程和非核心线程进行释放(调用poll方法,超出了keepAliveTime,自动释放)。
  • 第二种策略:使用shutdown方法关闭线程池来对核心线程和非核心线程进行释放(主动为Workers集合中的线程设置中断标识,自动释放)。

设置allowCoreThreadTimeOut为true或false,对线程回收影响

image

  • 如果设置allowCoreThreadTimeOut为true,那么不管你是否为核心线程还是非核心线程,到指定的keepAliveTime时间,那么就直接不阻塞,执行processWorkerExit方法释放掉。
  • 如果设置allowCoreThreadTimeOut为false,那么还需要判断如果没超过核心线程数,那么直接当前工作线程阻塞,如果超过核心线程数,那么直接将当前线程,按照指定的keepAliveTime时间,这段时间阻塞,到时间点了,那么不阻塞了,执行processWorkerExit方法释放掉。

设置allowCoreThreadTimeOut为true,内部如何优雅的停掉线程

image

  • 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。

当线程池没有任务执行的时候,再进来任务的时候,如何处理的?

  • 先判断工作线程数是否达到核心线程数

  • 如果工作线程数已经超过了核心线程数,那么之前的工作线程肯定都处于阻塞状态,此时再进来一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务添加进了阻塞队列(工作线程数 > 核心线程数,进入阻塞队列),阻塞的线程发现来任务了,都开始争抢任务。

  • 如果工作线程数没有超过核心线程数,那么来了一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务直接使用我们自己的新创建的线程执行(工作线程数 < 核心线程数,直接执行)。

  • 至于我们是否设置allowCoreThreadTimeOut为true,只是控制核心线程数,如果没到keepAliveTime,可能会出现工作线程数等于核心线程数。如果到了keepAliveTime,可能会出现工作线程数小于核心线程数。

processWorkerExit方法如何回收线程

processWorkerExit方法 -> tryTerminate方法 -> interruptIdleWorkers方法
image

image

  • 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。

execute方法执行过程

  • 1. 工作线程数 < 核心线程数
    1.1 new Thread创建新线程作为当前工作线程,将当前工作线程Worker添加到workers集合
    1.2 启动当前线程(调用runWorker方法)
    1.2.1 获取当前线程,当前线程已执行完毕并释放,那么从阻塞队列获取任务(调用getTask)
    1.2.1.1 如果等待队列中存在了等待的任务,那么取出等待队列中第一个任务返回。
    1.2.1.2 如果超过核心线程数的线程,那么timed = true,同时队列中已经没有任务了,那么等待keepalivetime秒就释放。
    1.2.1.3 如果不超过(等于)核心线程数的线程,那么timed=false,同时队列中已经没有任务了,那么执行take方法阻塞。
    1.2.1.3.1 如果此时进来一个新任务,那么发现阻塞线程数(工作线程数)不小于核心线程数,那么直接执行offer插入等待队列,等待队列有值了,阻塞的线程们又开始抢任务干活了。
    1.2.2 执行我们自己的业务逻辑(调用run方法)
    1.2.3 移除workers中的当前线程,回收线程(调用processWorkerExit方法)
    1.3 移除workers中的当前线程,回收线程(调用addWorkerFailed方法)
  • 2. 工作线程数 > 核心线程数
    将当前线程加入到等待队列BlockQueue。
  • 3. 等待队列满了
    直接创建新线程作为工作线程执行逻辑。
  • 4. 工作线程数 > 最大线程数
    抛出拒绝策略异常

线程池中线程异常处理

public class Task implements  Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 进入了task方法!!!");
        int i=1/0;
    }
}

execute会抛出异常

public static void main(String[] args) {

	ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
			keepAliveSeconds, TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
			new ThreadPoolExecutor.CallerRunsPolicy() {
				@Override
				public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
					if (!e.isShutdown()) {
						r.run();
					}
				}
			});
	executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);

	//当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
	executor.execute(new Task());
}

image

submit会在返回时抛出异常

public static void main(String[] args) {

	ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
			keepAliveSeconds, TimeUnit.SECONDS,
			new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
			new ThreadPoolExecutor.CallerRunsPolicy() {
				@Override
				public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
					if (!e.isShutdown()) {
						r.run();
					}
				}
			});
	executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);

	//当线程池抛出异常后 submit无提示,其他线程继续执行
	executor.submit(new Task());
}

image

线程池中多线程下载任务,其中出现一些线程抛出异常,事务回滚如何处理?

  • 思路:可以将每次线程执行成功的操作记录到数据库中,然后当所有的线程任务执行完毕,那么将最终的执行结果还原,保证最终一致性。

生产上如何配置使用线程池

代码
/**
 * 线程池配置类
 * @author sunpeiyu
 * @date 2023-04-10
 */
@Configuration
public class ThreadConfig {

    /**
     * 最大线程数量
     */
    private static final int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
    /**
     * 核心线程数量
     */
    private static final int corePoolSize = maxPoolSize - 1;
    /**
     * 空闲线程存活时间
     */
    private static final int keepAliveSeconds = 60;
    /**
     * 线程阻塞队列容量
     */
    private static final int queueCapacity = 1000;
    /**
     * 是否允许核心线程超时
     */
    private static final boolean allowCoreThreadTimeOut = false;
    /**
     * 线程前缀
     */
    private static final String THREAD_PREFIX = "DOWNLOADER_";

    @Bean
    @Scope("singleton")
    public ThreadPoolExecutor getExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
                keepAliveSeconds, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(queueCapacity), new InnerThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                        if (!e.isShutdown()) {
                            r.run();
                        }
                    }
                });
        executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);
        return executor;
    }


    static class InnerThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            LocalDateTime ldt = LocalDateTime.now();
            DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyyMMd HH:mm:ss");
            thread.setName(THREAD_PREFIX + ldt.format(pattern));
            return thread;
        }
    }
}

线程池预热

prestartAllCoreThreads()
启动所有核心线程,导致他们等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。

prestartCoreThread()
启动核心线程,使其无法等待工作。 这将覆盖仅在执行新任务时启动核心线程的默认策略。 如果所有核心线程已经启动,此方法将返回false 。

线程池监控

image

参考

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

posted @ 2023-05-07 21:40  sunpeiyu  阅读(15)  评论(0编辑  收藏  举报