线程池相关知识点
ThreadLocal
含义
每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
Thread线程内部的Map在类中描述如下:
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
实现场景
当用户完成登录时, 用户的信息会被存入threadlocal存储区, 从而充当全局变量的角色
public class LoginInfoHelper {
private static ThreadLocal<LoginInfo> local = new ThreadLocal<>();
public static void setUserInfo(LoginInfo loginInfo) {
local.set(loginInfo);
}
public static LoginInfo getUserInfo() {
return local.get();
}
public static void removeUserInfo() {
local.remove();
}
}
方法
get函数负责获取当前线程的副本变量值;set函数负责保存当前线程的副本变量值;initialValue属性表示当前线程初始设置的副本变量值;remove函数删除当前线程的副本变量值。
原理&底层实现
核心变量被存储于当前线程的 ThreadLocalMap 中
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { … }
如何确保Local属性?ThreadLocal为何能达到如此出色的效果?因为当一个线程调用ThreadLocal.get()方法时,该类实例会在创建时为每个参与的线程分配一个独立的数组副本,并通过维护一个基于当前线程ID的索引字段来快速定位对应的值存储位置。这些独立的数据副本之间互不影响且相互隔离,在访问过程中不会产生数据冲突或干扰。
内存泄漏
其中在ThreadLocalMap中同样采用Entry来存储K-V结构的数据。然而,在Entry中存储的关键值对中的键只能是ThreadLocal对象这一点已经被Entry的构造方法所严格限定。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Entry基于WeakReference(一种弱引用数据结构,在生命周期内只会存活到下一次垃圾回收操作之前),其中虽然Key字段采用了这种弱引用特性以延长其有效存活周期( lifetime),但其Value字段并未采用这种设计方式
因为Key采用的是弱引用机制,在执行完ThreadLocal的get()和set()操作后立即调用remove方法以断开其与Entry节点及Map的引用关系。这样一来,在GC Roots分析之后,整个Entry对象将不再成为可达对象;等到下一次 garbage collection(垃圾回收)时自然会被回收。
如果在使用ThreadLocal的set方法后没有显式地invoke remove方法,则可能会导致内存泄漏;因此养成良好的编程习惯至关重要,在使用完ThreadLocal之后请记得invoke remove method以避免潜在的问题。
参考博客地址:https://www.jianshu.com/p/98b68c97df9b
了解资料:
强引用:垃圾回收器不会去回收
软引用:如果内存空间不足,会被回收,可以用来实现内存敏感的高速缓存,加速回收速度,防止OOM问题
弱引用:弱引用有更短的生命周期,如果被垃圾回收器线程扫描到,就被回收
虚引用:在任何时刻都可能被回收,虚引用主要用来跟踪对象被垃圾回收的活动
**内存泄露**:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
**内存溢出**:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
线程池基本知识点
线程池的种类,区别和使用场景?
CachedThreadPool
- 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。里面没有核心线程,全是非核心线程。超过60秒的空闲线程就会被回收,当线程池都处于闲置状态时,线程池中的线程都会因为超时而被回收,所以几乎不会占用什么系统资源。
- 适用:执行很多短期异步的小程序或者负载较轻的服务器,适合任务量大但耗时少的任务。
FixedThreadPool:
- 通俗:本系统设计了一个预先定义最大线程数的队列池结构,在每个线程存活周期内不会中断(即存活时间无上限),当队列已满不再继续添加新线程;当队列中的所有核心线程均陷入繁忙状态时(即阻塞队列达到最大容量),新任务将被queued到无界阻塞队列中(即new LinkedBlockingQueue()),这种情况下属于无解阻塞队列状态;该系统内部仅有核心线程运行,在非核心区域没有任何资源进行处理任务;所有任务大小均无限制;即使处于空闲状态也不会回收任何空闲线程;除非整个队列被关闭。
- 适用:该方案特别适用于执行长时间运行的任务场景,在性能上相比传统方法有显著提升;特别适合那些任务量固定但运行时间较长的任务类型。
ScheduledThreadPool:
- 通俗:建立一个固定容量的多线程集合(即多核进程),其中每个子进程(即单个程序)运行时间不受限制;该集合能够自动处理定期和循环执行的任务(即定时或周期性任务)。当所有核心进程均处于高负载状态时(即系统资源被耗尽),新任务将被暂时搁置并在延迟队列中等待处理;该延迟队列采用按超时等待时间排序处理的方式(即优先级由剩余等待时间决定)。其中的核心进程数量固定设置为指定值;非核心进程则无数量限制,并且一旦处于空闲状态就会被系统回收资源。
- 适用:适用于需要定期运行的任务场景(如Web服务器、数据采集工具等),特别适合用于执行定时任务。
SingleThreadExecutor:
- 通俗:建立单个线程的并行计算框架;该并行计算框架中的工作单元永远存活;当工作单元被当前 busy task 占据时,则将新 task 放入阻塞队列中(无界阻塞队列)。通过将所有 task 集中的工作交给同一工作单元完成处理,则无需单独管理工作单元间的同步问题。
- 适用:逐个处理每个 task 的计算模式。这类并行计算框架适用于按顺序处理多个相同类型 task 的场景。
线程池的种类,区别和使用场景?
线程池的工作过程如下:
当线程池首次启动时,并无任何运行中的线程存在。所有待处理的任务都会被预先加入队列中等待执行。值得注意的是,在这种情况下即使存在未完成的任务也不会立即被处理而是等待后续资源的释放。
当向execute()方法提交一项新任务时系统将执行以下操作:
若当前运行中的线程数量少于核心配置数则立即生成新的核心进程并将其分配到该任务上;否则系统将该任务暂时搁置于队列中等待处理。
在上述情况发生后如果发现队列已达到最大容量限制则会根据当前运行中的进程数量决定是否立即生成新的非核心进程来进行处理。
特别地如果此时不仅已经满了而且现有进程数目低于最大配置值则系统仍会选择生成一个新的非核心进程来接替;但如果现有进程数目已经达到最大配置值则系统将无法继续执行新请求而会抛出相应的异常信息提示用户当前资源已满载状态。
- 某个线程完成任务后,在队列中等待下一个任务来进行处理。
- 某条线程处于空闲状态时,在经过预定时间 keepAliveTime 后会被检测到。此时如果当前运行中的线程数量超过设定的最大值 corePoolSize,则该条线程会被终止掉。因此,在所有子任务均完成之后,在这种多态式队列模型下运行的进程数目将缩减至预设的最大数量 corePoolSize。
线程池的实现过程未使用Synchronized关键字,并主要采用了[Volte]等技术配合Lock、阻塞队列以及与原子操作相关的类,并加入FutureTask等辅助功能以提升性能优势
ThreadPoolExecutor
该类调度器ScheduledThreadPoolExecutor可视为线程执行器Threaded excitator的一个延伸,在其基础之上增加了定时调度任务的核心功能,并且也支持按照预定时间间隔进行任务调度的功能。
借助线程池系统能够有效地实现对多任务环境下的资源优化配置,从而实现对运行效率和稳定性指标的有效调控。针对如何实现资源的有效利用问题已经在前面讨论过,其具体的执行流程自然融入整个系统的运行机制中,这一机制本身具有较高的可扩展性
在ThreadPoolExecutor中有一个叫做ctl的AtomicInteger变量。该变量用于存储了两项数据
- 所有线程的数量
- 每个线程所处的状态
线程池如何调优,最大数目如何确认?
配置线程池规模是优化系统性能的核心考量因素之一。不同配置下的系统性能表现存在显著差异,在特定情况下(如高负载场景),过大的线程池规模可能导致负面效果。
一般我们会从以下几个方面对线程池进行设置:
设置最大线程数
设置最小线程数
线程池任务大小
设置ThreadPoolExecutor的大小
根据任务性质的不同:建议为CPU密集型任务分配最少数量的线程,并例如将线程数量设置为CPU核心数加一;而为了充分利用IO资源并避免资源闲置,则推荐将线程数量最大化至两倍于现有核心数加一;对于混合性质的任务:如果能够分解,则将其分为上述两种类型分别进行优化处理;但若两种类型所需时间差异较大,则无需进行这种分解处理。
任务受限于其他系统资源:例如某个任务依赖数据库的连接返回结果时,则当等待时间越长,则CPU空闲时间越长,则需要将线程数量设置得越多才能更好地充分利用CPU资源。
当进程等待时间占比越大时
如果线程CPU时间所占比例越高,说明CPU比较繁忙,此时需要越少线程。
另外,如果线程数量过多,线程之间的切换也会带来开销。
线程池工具类的类图

创建线程池方式
通过其构造函数实现线程池的创建(参考自《阿里巴巴Java开发手册》),该方法帮助开发者清晰理解线程池的操作机制,并有效避免因资源耗尽而导致的问题

可以看到有四个构造方法,其代码如下,
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
二、Executors的方式
1、使用方法
Executors类提供了下面的构造方法,

我们再挑选ExecutorService中的方法看下其具体实现,
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过查看上述代码, 可以看出该函数返回的对象是ThreaPoolExecutor对象. 该函数调用的是类中的一个构造方法, 具体为四个构造方法中的第一个. 其本质是通过类中的一个构造方法来进行操作.
使用
ExecutorService service = Executors.newFixedThreadPool(2);
三、ForkJoinPool线程池创建
//创建Taks
SumTask task = new SumTask(nums,0,nums.length);
//创建线程池
ForkJoinPool pool = new ForkJoinPool();
//提交任务,存在返回值
ForkJoinTask<Integer> future = pool.submit(task);
CompletableFuture 和 ForkJoinPool
CompletableFuture含义
用于表示异步操作的结果,并且提供了检测计算完成、等待处理完毕以及检索结果的处理完毕等方法。简单来说就是提供了一个用于建模异步运算结果的机制。它能够将耗时的操作从当前执行线程中分离出来,在操作完成后就进行回调。就像我们在饭店吃饭...比如说在饭店里吃饭的时候,并不需要亲自去做饭的工作,在饭做好后就可以去就餐了。CompletableFuture是JDK8中提出的一种支持非阻塞功能的强大工具,并且它同样实现了Future接口
JDK
CompletableFuture使用场景
CompletableFuture底层原理
参考博客 :
https://www.jianshu.com/p/44fc98657e3e
