Java并发篇—线程池入门扫盲指南

Java并发篇—线程池入门扫盲指南

主要处理流程:

(1)判断核心线程池是否已满,如果还没满则创建线程执行任务,否则进入下一步

(2)判断工作队列是否已满,如果没满则将该任务放入工作队列等待线程来执行,否则进入下一步

(3)判断线程池中的线程是否都处于工作状态,如果不是则新创建一个线程来执行任务,没有处于工作状态的线程被淘汰,否则按照饱和策略处理该任务。

ThreadPoolExecutor实现原理

执行流程

Java并发篇—线程池入门扫盲指南

1:当一个任务提交时,如果 CorePool 中的核心线程少于 CorePoolSize ,则创建一个新线程执行任务( 需要全局

2:如果CorePool中没有空闲的线程,那么加入BlockingQueue等待核心线程拉取任务执行

3:如果BlockingQueue已满,创建新线程后如果大于 maximumPoolSize 就跳转到4拒绝执行任务,如果小于就创建新线程执行任务( 需要全局锁

4:四种不同的拒绝策略,通过 rejectedExecution() 方法执行。

需要获取全局锁导致线程池的性能大大下降,应该尽量避免产生步骤1和步骤3

execute() 源码分析

public void execute(Runnable command) {
    // 如果任务为空则抛出异常
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 1.工作线程数小于核心线程池大小则添加工作线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        // 更新线程池工作状态
        c = ctl.get();
    }
    // 2.判断线程池是否处于工作状态,如果是则尝试把任务放入阻塞队列中
    if (isRunning(c) && workQueue.offer(command)) {
        // 3.再次检查是否应该回滚任务添加线程,防止任务放入阻塞队列后线程池down或者工作线程死亡
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            //double check时线程池down了,该任务无法执行
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 4.无法加入阻塞队列或者线程池已down,拒绝任务执行
    else if (!addWorker(command, false))
        reject(command);
}
复制代码

工作线程Worker执行流程

Java并发篇—线程池入门扫盲指南

1: execute()方法创建一个 Worker 线程执行当前任务

2:如果 Worker 线程数目等于 CorePoolSize ,则加入到阻塞队列中,等待 Worker 取出来执行任务

3:如果 BlockingQueue 已满,如果 Worker 线程数小于 maximumPoolSize 则创建新线程执行任务

4:反复执行(1)(2)(3)

线程池的使用

重点是设置参数,参数设得好,线上没烦恼

围绕下面几个问题思考如何设置线程池

线程池的大小?例如在 2G 内存里配置一个线程池,如何设置线程池大小

执行的任务属于哪一种类型?例如IO密集、CPU密集······

任务是否具有优先级?例如任务的紧急程度、执行时间长短

任务是否具有依赖性?例如依赖数据库连接

创建线程池

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
复制代码

1. corePoolSize(线程池基本大小)

每次提交任务时,如果当前线程数小于 corePoolSize 就会创建一个新线程,即使线程池中有空闲线程,直到线程数等于 corePoolSize

可以提前创建好所有线程并启动,调用 prestartAllCoreThreads()

2. runnableTaskQueue(任务阻塞队列)

保存等待执行的任务队列,可以使用任意阻塞队列,例如 ArrayBlockingQueue , LinkedBlockingQueue , PriorityBlockingQueue , SynchronousQueue

3. maximumPoolSize(线程池最大数量)

线程池允许的最大线程数,阻塞队列中的任务 已满 (队列必须有界)时,下一个任务无法进入阻塞队列,如果线程池中的线程数小于最大线程数,则尝试在线程池中继续创建线程执行任务

4. ThreadFactory(创建线程的工厂)

给每个线程设置更有意义的名字的一个工厂,例如 ThreadFactoryBuilder 工厂

5. RejectedExecutionHandler(饱和策略)

任务拒绝执行时采用的拒绝策略,有四种:

AbortPolicy
CallerRunsPolicy
DiscardOldestPolicy
DiscardPolicy

6. keepAliveTime(线程活动生命时长)

线程池的工作线程空闲后可以保持存活的时长。如果任务很多且每个任务的执行时间很短,可以调大存活时长提高线程利用率。

7. TimeUnit(生命时长的计算单位)

DAYS / HOURS / MINUTES / MILLISECONDS (毫秒)/ MICROSECONDS (微妙)/ NANOSECONDS (纳秒)

提交任务

两个核心方法:有返回值的 submit() 和无返回的 execute()

submit()方法

线程池会返回一个 Future 对象,对象中存储着任务是否执行成功、返回结果等信息,调用 get() 方法可以获取返回值,在任务未完成前会一直阻塞,可以设置超时时长 get(long, TimeUnit) 防止一直阻塞。

Future<Object> future = executor.submit(hasReturnValueTask);
try {
	future.get();    
} catch(Exception e) {
    
} finally {
    // 关闭线程池
    executor.shutdown();
}

复制代码

关闭线程池

两个核心方法: shutdown()shutdownNow()

区别:

shutdownNow() 会将线程池状态设置为 STOP ,然后尝试终止所有正在执行任务的线程,并返回等待任务的任务列表

shutdown() 会将线程池状态设置为 SHUTDOWN ,然后中断所有没有正在执行任务的线程。

如何选择:

如果任务不一定执行完,可以使用 shutdownNow() ,否则使用 shutdown()

调用关闭方法后,调用 isShutdown() 方法就会返回 True ,但是确认线程池的关闭还需要调用 isTerminated 方法,这个方法表示所有的任务都已经关闭了。

if(isShutdown() && isTerminated()) {
	System.out.print("线程池已关闭!");
}
复制代码

合理地配置线程池

分析任务特性:

任务的性质:CPU密集、IO密集、混合型(配置线程数)

任务的优先级:高、中和低(配置阻塞队列)

任务的执行时间:长、中和短(配置线程空闲存活时长)

任务的依赖性:是否依赖其他系统资源,如数据库连接(配置线程数目)

重点说明任务的依赖性如何确定线程数目,如果线程需要依赖数据库连接,提交SQL后需要等待数据库返回结果,这个过程CPU是空闲的, CPU空闲时间越长,那么线程数应该设置得越大 ,可以更好地利用CPU。

最好 配置阻塞队列会有界阻塞队列 ,可以 监控线程池的工作状态 ,如果大量的任务放入阻塞队列则会不断报出任务拒绝信息。

线程池的监控

takeCount :线程池需要执行的任务数量

completedTaskCount :线程池在运行过程中已完成的任务数量

largestPoolSize :线程池曾经创建过的最大线程数量

getPoolSize :线程池的线程数量

getActiveCount :获取活动的线程数目

可以通过扩展线程池进行监控,重写 beforExecute()afterExecute()terminated() 方法。

总结

到这里为止,应该要掌握下面的知识,如果你能很流畅地回答,那么恭喜你,线程池的 基本原理 算是过关了

  1. 谈谈线程池的核心原理?
  2. 谈谈工作线程的执行流程?
  3. 创建线程池有哪些参数要设置,如何设置参数,考虑因素有哪些?
  4. 谈谈提交并执行任务的两个方法及它们的区别?
  5. 谈谈关闭线程池的两个方法及它们的区别?

总结不易,如果阅读完这篇文章对你有小小的收获,你的一个小小的点赞能让我高兴一整天!

巨人的肩膀:

《Java并发编程的艺术》

原文 

https://juejin.im/post/5e9c45bb6fb9a03c854630a2

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java并发篇—线程池入门扫盲指南

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址