ThreadLocal源码分析

概述

一个线程可以设置多个线程局部变量,多个线程局部变量ThreadLocal保存在Thread#ThreadLocalMap中。注意,通过ThreadLocal#get()方法保存的值,并不是保存在ThreadLocal的内部变量中,而是以该ThreadLocal为key(其实是以WeakReference<ThreadLocal>为key),以该值为value,保存在currenthread的ThreadLocalMap中。

Thread

每个Thread中都引用了一个ThreadLocalMap。

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

 

ThreadLocalMap

ThreadLocalMap使用线性探测法解决hash冲突

构造函数

/**
         * 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) {
//初始化Entry[]数组
            table = new Entry[INITIAL_CAPACITY];
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//Entry[]中对应下标的元素赋值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

get方法

/**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];

//如果没有发生hash冲突,值相等,则直接返回,否则线性探测寻找下一个entry
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

发生冲突,开始线性探测:循环寻找下一个entry的下标,直至值相等才返回。

      /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
//循环寻找下一个entry的下标,直至值相等才返回
            while (e != null) {
                ThreadLocal<?> k = e.get();
//如果值相等,返回
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
//否则获取下一个entry的下标
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

set方法

/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        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;
//使用key.hashCode&(len -1)获取key在Entry[]中的下标
            int i = key.threadLocalHashCode & (len-1);
//从该下标位置处开始线性探测,直至寻找到一个下标位置为null的Entry为止
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
//在下标位置为null的Entry处赋值
            tab[i] = new Entry(key, value);
            int sz = ++size;
//赋值后判断是否需要重哈希扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap的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;
            }
        }

ThreadLocalMap清除过期Entry(避免内存泄漏)

上文也提到了,Entry继承自WeakReference,大家都知道WeakReference(弱引用)的特性,只要从根集出发的引用中没有有效引用指向该对象,则该对象就可以被回收,这里的有效引用并不包含WeakReference,所以弱引用不影响对象被GC。
这里被WeakReference引用的对象是哪个呢?可以看Entry的构造方法,很容易看出指的是ThreadLocal自身,也就是说ThreadLocal自身的回收不受ThreadLocalMap的这个弱引用的影响,让用户减轻GC的烦恼。

但是不用做些什么吗?这么简单?
其实不然,ThreadLocalMap还做了其他的工作,试想一下,ThreadLocal对象如果外界没有有效引用,是能够被GC,但是Entry不能自动被GC,Entry还被ThreadLocalMap的table数组强引用着呢,Entry和Entry.value都无法被回收。
所以ThreadLocalMap该做点什么?
我看看ThreadLocalMap的expungeStaleEntry这个方法,这个方法在ThreadLocalMap的get、set、remove、rehash等方法都会调用到,看下面标红的两处代码,第一处是将remove的entry赋空,第二次处是找到已经被GC的ThreadLocal,然后会清理掉table数组对entry的引用。这样entry在后续的GC中就会被回收。

/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
//让指定下标位置的Entry.value和Entry能被回收
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

 

ThreadLocal

get()方法

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
//获取currentThread的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
//获取ThreadLocalMap中该ThreadLocal对应的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

set()方法

**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
//获取currentThread的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
//设置该ThreadLocal对应的value
            map.set(this, value);
        else
            createMap(t, value);
    }

remove()方法

 如果数据初始化好之后,一直不调用get、set等方法,这样Entry就一直不能回收,导致内存泄漏。所以一旦数据不使用最好主动remove。

/**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocal的使用示例

Hibernate使用ThreadLocal管理多线程访问的部分代码如下

public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() {
  Session s = (Session)session.get();
  //open a new session,if this session has none
 if(s == null){
   s = sessionFactory.openSession();
   session.set(s);
 }
  return s;
}

1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。

如果不初始化initialvalue,则initialvalue返回null。

3。session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s(数据库连接)。

5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。

6。创建一个数据库连接实例 s

7。保存该数据库连接s到ThreadLocal中。

8。如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。

 

参考:ThreadLocal实现原理详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值