1 ThreadLocal概述
- ThreadLocal可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题
- ThreadLocal与方法中的局部变量很像,不同的是ThreadLocal可以实现线程内的资源共享,如多个方法共享同一个资源
2 ThreadLocal使用
示例代码
@Slf4j
public class TestThreadLocal {
public static void main(String[] args) {
test();
}
// 一个线程内调用, 得到的是同一个Connection对象
// 多个线程内调用, 得到的是各自的Connection对象
private static void test() {
for (int i = 0; i < 2; i++) {
new Thread(() -> {
log.info("{}", Utils.getConnection());
log.info("{}", Utils.getConnection());
log.info("{}", Utils.getConnection());
}, "t" + (i + 1)).start();
}
}
static class Utils {
private static final ThreadLocal<Connection> tl = new ThreadLocal<>();
public static Connection getConnection() {
// 到当前线程获取资源
Connection conn = tl.get();
if (conn == null) {
// 创建新的连接对象
conn = innerGetConnection();
// 将资源存入当前线程
tl.set(conn);
}
return conn;
}
private static Connection innerGetConnection() {
try {
return DriverManager.getConnection("jdbc:mysql://rm-uf6vsug12gq824oye1o.mysql.rds.aliyuncs.com:3306/health_management?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8", "q1997333", "Gyqs715spa");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
- 上述代码输出
11:35:51.316 [t1] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@787d01bf
11:35:51.316 [t2] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@2467b0c5
11:35:51.319 [t1] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@787d01bf
11:35:51.319 [t1] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@787d01bf
11:35:51.319 [t2] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@2467b0c5
11:35:51.319 [t2] INFO person.cyc.springbootdemo.testcode.testthreadlocal.TestThreadLocal - com.mysql.cj.jdbc.ConnectionImpl@2467b0c5
3 ThreadLocal原理
- ThreadLocalMap
每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
**ThreadLocal::set()**
- 调用
ThreadLocal::set()
方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中 - 源码
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
- 调用
**ThreadLocal::get()**
调用ThreadLocal::get()方法,就是以ThreadLocal自己作为key,到当前线程的ThreadLocalMap中查找关联的资源值
**ThreadLocal::remove()**
调用ThreadLocal::remove()方法,就是以ThreadLocal自己作为key,移除当前线程的ThreadLocalMap中相关的资源
4 ThreadLocalMap
- ThreadLocalMap数据结构
- ThreadLocalMap底层的数据结构是hash表
- key的hash值统一分配
- 初始容量16,扩容因子是2/3,扩容时容量翻倍
- key索引冲突后用开放寻址法解决冲突(冲突后顺序找下一个空闲位置)而不是拉链法
- ThreadLocalMap中的key是弱引用key
- Thread可能需要长时间运行(如线程池中的核心线程),如果key不再使用,需要在内存不足(GC)时释放其占用的内存
- 当线程内没有其他地方引用某个ThreadLocal后,ThreadLocalMap中的key也就丧失了作用(因为不会再被查询),所以下一次GC的时候就可以被回收了,因此ThreadLocalMap中的key是弱引用
- 强引用(Strongly Reference)
- 强引用就是传统的引用定义,即类似
Object obj = new Object()
的引用关系 - 无论任何情况下,只要强引用关系还存在,GC就永远不会回收掉被强引用的对象。
- 强引用就是传统的引用定义,即类似
- 软引用(Soft Reference)
- 软引用用来描述一些“还有用,但非必须的对象”。
- 只被软引用关联着的对象,在系统将要发生内存溢出异常前,GC会把这些对象列进回收范围,然后进行第二次回收。如果第二次回收后还是没有足够的内存,才会抛出内存溢出异常
- 在JDK1.2后,Java提供了
**SoftReference**
类来实现软引用
- 弱引用(Weak Reference)
- 弱引用也是用来描述那些非必须的对象,但是强度弱于软引用
- 被弱引用关联的对象只能生存到下一次垃圾回收前,即当GC开始工作时,无论内存是否充足,都会回收只被弱引用关联的对象
- 在JDK1.2之后,Java提供了
**WeakReference**
类来实现弱引用
- 虚引用(Phantom Reference)
- 虚引用也被称为“幽灵引用”,是最弱的一种引用关系
- 一个对象是否有虚引用,完全不影响其生存时间,也无法通过虚引用来获取一个对象实例
- 为对象设置虚引用的目的只是为了能在这个对象被回收前收到一个系统通知
- JDK1.2之后,Java提供了PhantomReference来实现虚引用
- 强引用(Strongly Reference)
- ThreadLocalMap中key和value内存释放时机
被动方式
- 被动GC释放key
仅是让key的内存释放,关联value的内存并不会释放
- 懒惰被动释放value
- GC让key内存释放,后续要根据key是否为null来进一步释放value内存
get(key)
时,发现是null key,则释放其value内存set(key, value)
时,会使用启发式扫描,清除临近的null key的value内存,启发次数与元素个数与是否发现null key有关
主动方式
- 主动remove释放key,value
- 会同时释放key,value的内存,也会清除临近的null key的value内存
- 推荐使用,因为一般使用ThreadLocal时都把它作为静态变量(即强引用),因此无法被动依靠GC回收