是什么ThreadLocal即线程局部变量提供了线程本地实例。同一个ThreadLocal 所包含的对象在不同的Thread中有不同的副本即不同线程之间是隔离的不存在多线程间的共享问题。ThreadLocal是一个泛型类保证可以接受任何类型的对象。ThreadLocalMap是其对应的一个静态内部类以ThreadLocal对象为键、任意对象为值的存储结构。public class ThreadLocalT { static class ThreadLocalMap { static class Entry extends WeakReferenceThreadLocal? { Object value; Entry(ThreadLocal? k, Object v) { super(k); value v; } } private static final int INITIAL_CAPACITY 16; private Entry[] table; private int size 0; .... } }使用get、set方法可以操作当前线程对应ThreadLocal实际是操作的ThreadLocalMap对应ThreadLocal为key的valuepublic 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(); } public void set(T value) { Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); if (map ! null) { // 将当前ThreadLocal作为key map.set(this, value); } else { createMap(t, value); } } // 获取当前线程的threadLocals变量 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals new ThreadLocalMap(this, firstValue); }Thread应用线程类中声明了一个ThreadLocal.ThreadLocalMap类型的成员变量是为了维护线程本地变量而设计的。public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals null; ... }Thread的threadLocals本身不是开发直接调用的它是 Thread 内部的一个成员变量类型是ThreadLocal.ThreadLocalMap。我们平时用的 ThreadLocal.set()、get()、remove()本质上都是在操作当前线程对象里的这个 threadLocals。整体结构可以理解为Thread └─ threadLocals: ThreadLocalMap ├─ Entry(ThreadLocalA, valueA) ├─ Entry(ThreadLocalB, valueB) └─ Entry(ThreadLocalC, valueC)内存泄漏问题ThreadLocalMap的Entry类中key为ThreadLocal的弱引用特点就是如果这个对象只存在弱引用那么在下一次垃圾回收的时候必然会被清理掉。key 用弱引用的目的是当外部不再持有 ThreadLocal 实例时GC 可以回收这个 key避免 ThreadLocal 对象本身长期泄漏。java对象的引用包括 强引用软引用弱引用虚引用 。强引用是使用最普遍的引用。如果一个对象具有强引用那垃圾回收器绝不会回收它当内存空间不足时Java虚拟机宁愿抛出OutOfMemoryError错误使程序异常终止也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果一个对象只具有软引用则内存空间充足时垃圾回收器就不会回收它如果内存空间不足了就会回收这些对象的内存。线程扫描内存区域时一旦发现了只具有弱引用的对象不管当前内存空间足够与否都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程因此不一定会很快发现那些只具有弱引用的对象。虚引用顾名思义就是形同虚设。与其他几种引用都不同虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用那么它就和没有任何引用一样在任何时候都可能被垃圾回收器回收。所以如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候会被清理掉的这样 ThreadLocalMap中使用这个 ThreadLocal 的 key也会被清理掉。但是value 是强引用不会被清理就会出现 key 为 null 的 value当value是很大的对象就会存在内存泄漏风险。ThreadLocalMap实现中已经考虑了这种情况在调用 set()、get()、remove() 方法的时候会清理掉 key 为 null 的记录。如果说会出现内存泄漏那只有在出现了 key 为 null 的记录后没有手动调用 remove() 方法并且之后也不再调用 get()、set()、remove() 方法的情况下。正确写法private static final ThreadLocalUser USER_HOLDER new ThreadLocal(); try { USER_HOLDER.set(user); // 业务逻辑 } finally { USER_HOLDER.remove(); // 必须清理 }常见使用场景用户上下文传递ThreadLocal定义用户上下文public class UserContext { private static final ThreadLocalUser USER_HOLDER new ThreadLocal(); public static void setUser(User user) { USER_HOLDER.set(user); } public static User getUser() { return USER_HOLDER.get(); } public static void clear() { USER_HOLDER.remove(); } }过滤器中设置与清理public class UserFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { try { User user parseUserFromRequest((HttpServletRequest) req); UserContext.setUser(user); chain.doFilter(req, res); } finally { UserContext.clear(); // 必须清理防止线程池复用导致数据串号 } } }业务侧使用Service public class OrderService { public void createOrder(Order order) { User user UserContext.getUser(); // 不需要再把 userId 作为参数传进来 order.setCreateBy(user.getId()); orderDao.insert(order); } }数据库连接与事务public class ConnectionManager { private static final ThreadLocalConnection HOLDER new ThreadLocal(); private static DataSource dataSource; public static Connection getConnection() throws SQLException { Connection conn HOLDER.get(); if (conn null || conn.isClosed()) { conn dataSource.getConnection(); HOLDER.set(conn); } return conn; } public static void beginTransaction() throws SQLException { getConnection().setAutoCommit(false); } public static void commit() throws SQLException { Connection conn HOLDER.get(); if (conn ! null) { conn.commit(); conn.setAutoCommit(true); } } public static void rollback() { try { Connection conn HOLDER.get(); if (conn ! null) { conn.rollback(); conn.setAutoCommit(true); } } catch (SQLException ignored) {} } public static void close() throws SQLException { Connection conn HOLDER.get(); if (conn ! null) { conn.close(); HOLDER.remove(); } } }日志链路追踪public class TraceContext { private static final ThreadLocalString TRACE_HOLDER new ThreadLocal(); public static void setTraceId(String traceId) { TRACE_HOLDER.set(traceId); } public static String getTraceId() { return TRACE_HOLDER.get(); } public static void clear() { TRACE_HOLDER.remove(); } }TraceId使用log.info([{}] 开始处理订单orderId{}, TraceContext.getTraceId(), orderId);日志框架接入 MDCMDC 本身是 SLF4J 提供的线程级上下文容器底层依赖ThreadLocal。日志要能自动带上 traceId必须修改日志格式。
阅读完成 · 觉得有帮助?