一、ThreadLocal 简介
ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点:
A、当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。
二、ThreadLocal 特点及用途:
1.ThreadLocal是单线程内共享资源,多线程间无法共享(即线程A访问不了线程B中ThreadLocal存放的值);
2.ThreadLocal是本地变量,无法跨jvm传递;
3.ThreadLocal的出现可以减少通过参数来传递(使代码更加简洁,降低耦合性),Hibernate中的OpenSessionInView,就始终保证当前线程只有一个在使用中的Connection(或Hibernate Session),代码如下:
1 public class ConnectionManager { 2 3 /** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */ 4 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); 5 6 public static Connection getCurrConnection() { 7 // 获取当前线程内共享的Connection 8 Connection conn = threadLocal.get(); 9 try { 10 // 判断连接是否可用 11 if(conn == null || conn.isClosed()) { 12 // 创建新的Connection赋值给conn(略) 13 // 保存Connection 14 threadLocal.set(conn); 15 } 16 } catch (SQLException e) { 17 // 异常处理 18 } 19 return conn; 20 } 21 22 /** 23 * 关闭当前数据库连接 24 */ 25 public static void close() { 26 // 获取当前线程内共享的Connection 27 Connection conn = threadLocal.get(); 28 try { 29 // 判断是否已经关闭 30 if(conn != null && !conn.isClosed()) { 31 // 关闭资源 32 conn.close(); 33 // 移除Connection 34 threadLocal.remove(); 35 conn = null; 36 } 37 } catch (SQLException e) { 38 // 异常处理 39 } 40 } 41 }
三、ThreadLocal 实现原理
定义了一个Map(非普通map) 结构,
例如:
定义一个 ThreadLocal 对象
public static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
key 就是 integerThreadLocal
value 就是 Integer 类型的值
在 Thread 里面持有该对象的引用,也就是说,不同的线程,有各自的 ThreadLocalMap
例如:A 线程,有一个 ThreadLocalMap,key 是一系列 ThreadLocal 对象,value 是 A 线程使用这一系列 ThreadLocal 对应的值;同理,B 线程,也有一个 ThreadLocalMap,,key 是一系列 ThreadLocal 对象,value 是 B 线程使用这一系列 ThreadLocal 对应的值。
所以,现在有两个 ThreadLocal 对象,被3个线程 TA TB TC 使用:
public static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>(); public static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); TA:integerThreadLocal.set(1);stringThreadLocal.set(\"A\"); TB:integerThreadLocal.set(2);stringThreadLocal.set(\"B\"); TC:integerThreadLocal.set(3);stringThreadLocal.set(\"C\");
最终存储结构是:
{{\"thread\":TA,\"threadLocalMap\":{\"integerThreadLocal\":1,\"stringThreadLocal\":\"A\"}}, {\"thread\":TB,\"threadLocalMap\":{\"integerThreadLocal\":2,\"stringThreadLocal\":\"B\"}}, {\"thread\":TC,\"threadLocalMap\":{\"integerThreadLocal\":3,\"stringThreadLocal\":\"C\"}}} 格式化后,如下: { \"TA\": { \"integerThreadLocal\": 1, \"stringThreadLocal\": \"A\" }, \"TB\": { \"integerThreadLocal\": 2, \"stringThreadLocal\": \"B\" }, \"TC\": { \"integerThreadLocal\": 3, \"stringThreadLocal\": \"C\" } }
使用时:
stringThreadLocal.get();
四、ThreadLocal 的弱引用
强引用:
Object obj = new Object();
只要 obj 不为空,gc 的时候 就不会回收 obj,此为强引用。
弱引用:
WeakReference<Object> weakRef = new WeakReference<>(new Object());
发生 gc 时,尽管 括号里面的对象 不为空,也会被回收,但是 weakRef 是强引用,不会被回收。
ThreadLocalMap 的 key 是 ThreadLocal 对象的弱引用,即 gc 以后,ThreadLocalMap 的 key 将指向 null,而 value 依旧是强引用,不会被回收,会造成内存泄露。
所以,ThreadLocal 的弱引用,解决了内存泄露吗?
解决了一部分,key 确实被回收了,但是 value 不是弱引用,没有被回收,还是有可能造成内存泄露。
五、ThreadLocal 解决内存泄露
两种内存泄露情况:
1.发生了 gc,弱引用被回收了,ThreadLocalMap 的 key 指向了 null,而 value 是强引用,不会被回收,会造成内存泄露
2.线程被线程池管理,不会销毁,其对应的 ThreadLocalMap 也不会被回收,会造成内存泄露
解决方案:
1.主动调用 ThreadLocal 的 remove 方法,
2.ThreadLocal 在 set、get、remove 时,会自动移除 key 为 null 的 Entry。由于,ThreadLocalMap 的 key 是弱引用,gc 后 key 会指向 null,所以,这部分会被置为 null,方便下次 gc 时回收
来源:https://www.cnblogs.com/god-smile996/p/16333784.html
本站部分图文来源于网络,如有侵权请联系删除。