ThreadLocal解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: JDK1.2 就这样了ThreadLocal 为解决多种难题提供了一个新的思路;ThreadLocal的目的是为了解决多线程访问资源时的共享问题。这原来是搜索到的线程本地的故事都是这样写的。,谎言说下去就形成了真理。但在JDK文档里面

ThreadLocal的意义

JDK1.2 就这样了

ThreadLocal 为解决多种难题提供了一个新的思路;

ThreadLocal的目的是为了解决多线程访问资源时的共享问题。

这原来是搜索到的线程本地的故事都是这样写的。

,谎言说下去就形成了真理。

但在JDK文档里面

该类提供了其他(线程局部)变量。这些不同的角色,因为独立或设定自己的角色(通过它的方法获得)的普通人物角色,它属于角色的初始角色。ThreadLocal 实例通常是类中的私有静态字段,将状态与某个希望线程(例如,用户 ID 或事务 ID)相关联。

ThreadLocal的是提供线程内部的相同作用,这种线程在线程生命周期内起作用,减少一些线程内部函数或组件之间的一些复杂度。

ThreadLocal 和多线程模式并没有什么关系。

使用这些方法解决对象共享问题的,只是为了在一个业务线程中处理一个事务环境中的一个问题,需要在本地处理其他方法来处理其他问题的方法时,需要在线程中使用这些方法来处理其他问题时,在中共享问题时。

例如有方法a(),在该方法中调用了方法b(),而在b方法中又调用了方法c(),即a-->b--->c,如果a,b,c都需要使用就是用户对象,所以我们常用的做法a(User user)-->b(User user)---c(User user)。

如果使用ThreadLocal我们就可以使用另外一种解决方法:在示例中定义一个对象的ThreadLocal对象,例如 public static ThreadLocal threadLocal=new ThreadLocal();然后让a,b,c的方法是类A,类B,类C都实现1中的接口在调用a时,使用threadLocal.set(user) 把用户对象线程本地环境b,可以在这样的方法中使用方法,在方法中获取方法,在方法中获取用户对象。

上面的类A,类B,类C就可以分别是我们做web开发时的web层的Action--->逻辑业务层的Service-->数据访问层的DAO,当我们要在这个层中共享参数的时候,那么我们就可以使用ThreadLocal了。

在使用hibernate中常见的方法

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定义SessionFactory
    static {
        try {
            // 通过默认配置文件hibernate.cfg.xml创建SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失败!", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
    //创建线程局部变量session,用来保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
    /**
     * 获取当前线程中的Session
     * @return Session
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // 如果Session还没有打开,则新开一个Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //将新开的Session保存到线程局部变量中
        }
        return s;
    }
    public static void closeSession() throws HibernateException {
        //获取线程局部变量,并强制转换为Session类型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

Threadlocal源码

这个类有以下方法:

  1. get():返回当前线程的线程的值。
  2. 初始值:初始值的初始值。
  3. remove():当前激活的值很重要。
  4. set(T value):为当前线程设置一个特定线程的值。

get() 方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

可知当前通过线程,到达ThreadLocalMap,当前线程对象取到值。

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

Thread.java里面

ThreadLocalMap 是线程的属性

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

这个还跟之前的理解一样,以为是当前threadId为key,取到值的情况

数据结构是这样的:

线程本地

结构是这样的

线程 <-> 线程本地映射

Thread维护一个ThreadLocalMap映射表,这个映射表的是ThreadLocal存储本身,是真正需要的对象。

设计的主要有以下优势:

  1. 设计每个之前的项目是这样的
  2. 当线程对应不同的线程地图时,可以使用不同的线程地图。

智力动力

有人认为在这种情况下,容易出现OOM

image.png

如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的值,如果当前线程再迟迟不结束的话,null的Entry的value一直存在一条强引用链:

线程引用 -> 线程 -> ThraLocalMap -> 条目 -> 值

永远无法回收,成就事业。

进一步分析一下ThreadlocalMap

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

根据注释和代码,发现ThreadLocalMap并没有使用HashMap,而是重新实现了一个map,里面放的Entry

/**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            //默认16长度
            table = new Entry[INITIAL_CAPACITY];
            //hashcode取模,得到坑位
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
        private void set(ThreadLocal key, Object value) {
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len - 1);
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();
                if (k == key) {
                    e.value = value;
                    return;
                }
                //key在gc时,会被回收,变成null
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
                        //替换掉之前有key=null的
            for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

在这些过程中遇到的关键任务被认为是无效的,因此所有条目自然会被恢复为无效。

光这样还是这个设计思路的一个条件,要调用的Entry函数或者任何线程Map的设置函数。在这种情况下,需要手动调用线程调用函数,手动不再需要本地的线程,阻止本地的线程调用或本地移动。所以 JDK 建议将 ThreadLocal 变量定义为私有静态的,ThreadLocal 的生命周期会因为一直弱存在的弱引用而导致 ThreadLocal 的调用,然后保证 ThreadLocal 的值可以访问到,ThreadLocal 的值可以被调用,因此可以删除任何它, 投射目标

使用弱引用

为了帮助处理非常大且长期存在的使用,哈希表条目使用 WeakReferences 作为键。为了应对的非常大和长时间的用途,哈希表使用弱引用键

  • 关键使用引用:引用的Local的对象被回收了,但是ThreadLocalMap还没有支持Thread的强引用,手动删除,ThreadLocal不会被回收,导致ThreadLocal的条目被泄露。
  • 关键使用弱引用:由于引用的ThreadLocal的弱对象被回收了,由于ThreadLocalMap持有ThreadLocal的引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的会被清除的时候

比较不同的情况,我们可以发现:由于本地线程的生命周期但是同样长,不会删除本地密钥,跟显示通知,手动引用多个线程:如果引用线程都没有内存通知,的值在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

因此,ThreadLocal 存储漏洞的原因是:由于 ThreadLocalMap 的周期跟 Thread 一样长,所以没有手动删除密钥,导致内存泄漏,而不是因为弱引用。

总结

ThreadLocal的是提供线程内部的相同作用,这种线程在线程生命周期内起作用,减少一些线程内部函数或组件之间的一些复杂度。

  • 不是为了解决多线程同步之类的作用
  • ThreadLocal 变量定义成静态的,不要定义成私有的

参考资料

http://www.iteye.com/topic/617368

http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8 %83]%E8%A7%A3%E5%AF%86ThreadLocal/


目录
相关文章
|
6月前
|
缓存 监控 Java
ThreadLocal 源码解析get(),set(), remove()用不好容易内存泄漏
ThreadLocal 源码解析get(),set(), remove()用不好容易内存泄漏
83 1
|
6月前
|
存储 Java 中间件
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
|
6月前
|
缓存 前端开发 Java
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
169 1
|
5月前
|
存储 安全 Java
深入理解Java中的ThreadLocal机制:原理、方法与使用场景解析
深入理解Java中的ThreadLocal机制:原理、方法与使用场景解析
93 2
|
5月前
|
存储 安全 Java
《ThreadLocal使用与学习总结:》史上最详细由浅入深解析ThreadLocal
《ThreadLocal使用与学习总结:》史上最详细由浅入深解析ThreadLocal
47 0
|
Java
ThreadLocal全面解析
ThreadLocal全面解析
118 0
|
6月前
|
存储 Java 数据库连接
ThreadLocal的原理解析以及应用场景分析
ThreadLocal的原理解析以及应用场景分析 什么是ThreadLocal? ThreadLocal是Java中的一个线程管理工具,用于保证线程间数据的独立性。每个ThreadLocal对象可以存储一个线程局部变量,即对于同一个ThreadLocal对象,每个线程都有自己独立的变量。
|
存储 Java 数据安全/隐私保护
ThreadLocal的实现原理&源码解析
ThreadLocal是Java中的一个线程封闭机制,它提供了一种线程局部变量的解决方案,可以使每个线程都拥有自己独立的变量副本,互不干扰。
95 0
|
存储 算法 Java
ThreadLocal原理解析
ThreadLocal原理解析
107 1
ThreadLocal原理解析
|
存储 Java 索引
ThreadLocal原理解析(2):ThreadLocalMap源码解析
ThreadLocal原理解析(2):ThreadLocalMap源码解析
下一篇
无影云桌面