一、线程池
1.1.线程池的作用:
- 降低资源消耗,提高利用率,降低创建和销毁线程的消耗。
- 提高响应速度,线程可直接执行,而不是先创建线程,再执行。
- 提高线程的客观理性,线程是稀缺资源,使用线程池可以统一分配调优监控。
1.2.线程池参数
- corePoolSize 代表核心线程数,设置线程池的最大核心线程数为 CPU 数的两倍。
-
默认情况下,线程池在完成初始化之后,线程池中不会有任何线程。
-
线程池会等有任务来的时候先去创建核心线程
-
核心线程只要创建后常驻线程池。即使超出了线程保持的存活时间配置也不会销毁,一直等着新任务进来进行处理。
-
- maxinumPoolSize 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数。
- keepAliveTime 、 unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime 来设置空闲时间。
- workQueue队列用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程,直至到最大线程数maxinumPoolSize 后执行Handler 任务拒绝策略。
- Handler 任务拒绝策略,有两种情况:
- 第一种是当我们调用 shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。
- 另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这时也要拒绝。
- ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂。
1.3.线程池处理流程:
1.4.为什么是先将任务添加列队而不是先创建最大线程处理任务?
因为每次在创建新线程的时候,都是要获取全局锁的,这个时候其它的任务就得阻塞,影响了整体效率。
1.5.线程池中阻塞队列的作用?
1.6.线程池中线程复用原理:
二、ThreadLoacl原理
2.1、ThreadLoacl的原理
在一般的多线程访问共享变量的场景中,只是通过对共享变量加锁来保证一个时刻只有一个线程会对共享变量进行更新,而加锁则会导致系统性能的下降。
ThreadLocal在每个线程里面都维护一个私有的ThreadLocalMap成员变量来存储共享变量的副本,每个线程只对自己的副本进行更新操作保证各线程之间的数据安全,每个线程的数据都不会被另外的线程访问和破坏。通过以空间换时间的方法来提高多线程访问共享变量的安全性,并且避免系统性能下降。
ThreadLocalMap 由一个个 Entry 对象构成,Entry 继承自 WeakReference<ThreadLocal<?>> ,一个 Entry 由 ThreadLocal 对象和 Object 构成。由此可见, Entry 的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该 key就会被垃圾收集器回收。
2.2、ThreadLocal内存泄漏
内存泄漏:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露 堆积后果很严重,最终会导致内存不够(OOM)。
ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应的Key就会导致内存泄漏。
2.3、ThreadLocal使用强引用和弱引用的区别:
1.key 使用强引用
当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
2.key 使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。
2.4、ThreadLocal的get、set、remove方法:
- 当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。代码如下所示:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
- get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,去ThreadLocalMap查找,如果存在不为空的Entry,就返回Entry中的value,否则就会执行初始化并返回默认的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- remove方法的实现逻辑,还是先获取当前线程的ThreadLocalMap变量,如果存在就调用ThreadLocalMap的remove方法。ThreadLocalMap的存储就是数组的实现,因此需要确定元素的位置,找到Entry,把entry的键值对都设为null,最后也Entry也设置为null。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
2.5、ThreadLocal使用场景:
- 线程间数据隔离
- 线程的上下文传递: 在进行对象跨层传递或者跨线程调用场景中, 使用ThreadLocal可以避免多次传递,打破层次间的约束。比如把框架和中间件用到的用户信息和请求ID存在ThreadLocal里,方便后续的请求处理链路进行访问。
- 事物管理: 在一些需要手动管理事物的场景中,可以手动使用ThreadLocal来存储事物的上下文。
- 数据库连接:在使用数据库连接池的情况下,把数据库的连接存储在ThreadLocal里,方便每个线程独立管理数据库连接,避免线程间的冲突和竞争。比如:Mabatis中的SqlSession对象使用ThreadLocal存储数据库的会话信息。
2.6、ThreadLocal正确使用方法:
- 每次使用完ThreadLocal都调用它的remove()方法清除数据。
- 将ThreadLcoal变量强引用定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。