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方法)。
线程池生命周期


任务执行机制
任务机制1,任务调度

任务机制2,任务缓冲

任务机制3,任务申请
getTask方法:

任务机制4,任务拒绝
- AbortPolicy:直接抛出RejectedExecutionException异常。
 - CallerRunsPolicy:只用调用者所在线程来运行任务。
 - DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
 - DiscardPolicy:不处理,丢弃掉。
 
Worker线程管理机制
线程增加
addWorker方法:

线程回收
processWorkerExit方法:

线程执行任务
runWorker方法:

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

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

- 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。
 
当线程池没有任务执行的时候,再进来任务的时候,如何处理的?
- 
先判断工作线程数是否达到核心线程数
 - 
如果工作线程数已经超过了核心线程数,那么之前的工作线程肯定都处于阻塞状态,此时再进来一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务添加进了阻塞队列(工作线程数 > 核心线程数,进入阻塞队列),阻塞的线程发现来任务了,都开始争抢任务。
 - 
如果工作线程数没有超过核心线程数,那么来了一个任务,创建了一个Worker,又创建添加了新的工作线程,这样我们的任务直接使用我们自己的新创建的线程执行(工作线程数 < 核心线程数,直接执行)。
 - 
至于我们是否设置allowCoreThreadTimeOut为true,只是控制核心线程数,如果没到keepAliveTime,可能会出现工作线程数等于核心线程数。如果到了keepAliveTime,可能会出现工作线程数小于核心线程数。
 
processWorkerExit方法如何回收线程
processWorkerExit方法 -> tryTerminate方法 -> interruptIdleWorkers方法


- 先获取锁,然后给所有的线程都打上中断标记,最后释放锁。
 
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());
}

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());
}

线程池中多线程下载任务,其中出现一些线程抛出异常,事务回滚如何处理?
- 思路:可以将每次线程执行成功的操作记录到数据库中,然后当所有的线程任务执行完毕,那么将最终的执行结果还原,保证最终一致性。
 
生产上如何配置使用线程池
代码
/**
 * 线程池配置类
 * @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 。
线程池监控

参考
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
                    
                
                
            
        
浙公网安备 33010602011771号