Threadlocal 主要用于在多线程的情况下,每个线程可以保存一份本地的变量,该变量在线程之间相互独立,互不影响,并伴随着该线程的运行过程可以随时取出。
应用场景:比如,在web系统中,每个用户进行访问都有各自的不同的用户信息,可以在访问接收的时候做一个拦截器,把用户的信息放入到threadlocal中,在该线程的整个流程中,可以取出当前用户的相关信息,并且和其他线程不相互影响。
原理:每个线程都有一个threadlocalMap对象,在向threadlocal放入变量的时候,实际上是向每个线程的threadlocalMap中放入变量,放入的变量是key value形式的,其中key是当前的threadLocal对象,value是放入的变量。threadlocalMap中有个Entity的kev value 数组,该entity其中的key是弱引用,也就是存放threadLocal对象的地方。
源码分析
首先从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(); 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(); }
(1)先获取当前的线程
(2)然后获取当前线程的ThreadLocalMap ThreadLocalMap map = getMap(t);
如果是第一次调用该方法,之前没有set过值,那么map应该为null。会调用 setInitialValue()方法;
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
(1)初始化的时候value 是为null ,map根据上面第一次调用的话为null
(2)所有会调用createMap(t, value)方法
/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
(1)获取当前线程threadLocalMap 然后new 了一个
/** * 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) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
(1)该构造方法为threadLocalMap内部的构造方法,主要的值存放在table数组中
(2)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 获取数组的下标,& (INITIAL_CAPACITY - 1)相当于对数组的初始容量取模,下标值一定是在数组容量范围内的。
(3)因为是第一次初始化,数组的大小为1.
(4) setThreshold(INITIAL_CAPACITY); 设置重新分配数组的空间的阀值,为数组大小的2/3,超过这个数组,从新分配空间。
public 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(); }
接下来看最开始的那段代码 ,假设不是第一次调用,当map不为空的时候 ,会走到
ThreadLocalMap.Entry e = map.getEntry(this); 这段代码
/** * 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) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
(1)if (e != null && e.get() == key) 如果当前位置值存在,并且key也相等,直接返回该值。
(2)存在一种情况,在放入值得时候,key的位置i已经存在了,则用线性探测法,去找下一个位置,直到找到不存在的位置,
所以上面的判断if不成立的时候,会调用getEntryAfterMiss(key, i, e),该方法用线性探测的方式查找值。
/** * 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; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
(1)从 i的位置向下遍历整张表,因为table不会一定会有为null的位置,所以找不到则返回null,找到key相等的值则直接返回。
(2)因为key 为弱引用,所有当key为空的时候,调用方法expungeStaleEntry(i) 情况value的值,并且遍历整张表,清空所有key为null的值。
/** * 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 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; }
接下来我们看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(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
(1)第一次set的时候同理上面已经说过createMap相关
(2)我们只看不是第一次的情况,这时候map不为空,该方法会调用map.set(this, value)
/** * 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; 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; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }(1)查找到有相同的key 则value进行覆盖,没有相同的key ,则在 i的位置创建一个新的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); }
直接可以看m.remove(this)方法
/** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { 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)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
(1)直接遍历table,找到当前的key在table中位置
(2)e.clear() 把key 弱引用置为null
(3)expungeStaleEntry(i) 把所有key为null的 value值清空
ThreadLocal 可能写的不是很好,还有一些地方没有看的很明白,就没有在本文中细说。