Java 并发 ---ThreadLocal源码分析
2017-09-11 22:33
691 查看
ThreadLocal是什么
Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量如何创建ThreadLocal
ThreadLocal是一个类,那么可以直接new出来,并且可以指定其类型ThreadLocal<Integer>id=new ThreadLocal<Integer>();
ThreadLocal 常用方法
1、设置值id.set(1);
2、读取保存在ThreadLocal变量中的值:
Integer value = id.get();
3、从ThreadLocal中移除某个变量
id.remove();
ThreadLocal使用方法很简单,ThreadLocal就是一个变量,可以给这个变量赋值(set),取值(get),删除值(remove),只是这个变量和线程相关,同一段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是什么
static class ThreadLocalMap
是ThreadLocal的静态内部类,大致看一下,似乎还很复杂,在ThreadLocalMap的下还有一个静态内部类
static class Entry extends WeakReference<ThreadLocal<?>>
不过从名字我们可以看出来 ThreadLocalMap 应该是一个key-value的结构,那我们就来找找key和value是什么
/** * 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); }
这是ThreadLocalMap 的一个构造函数,从形参我们可以知道,key就是ThreadLocal,至于value暂时可以不用care,不过可以猜测是我们set时的value。
/** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0;
table是一个Entry的数组而table的大小是2的倍数,初始大小为16.到这里又要中断去看一下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; } }
Entry是ThreadLocalMap的一个静态内部类,继承了弱引用,key作为了WeakReference的引用,value是Entry的value,于WeakReference,在下一次GC的时候,会回收WeakReference关联的对象(如果该关联的对象不再被使用)
ThreadLocalMap的结构大致明白了,ThreadLocalMap确实是key-value结构,而这个结构是通过Entry来实现的,key是ThreadLocal 实例,value到这里还不确定(其实看一下set就很容易看出这个就是我们传入的value)。
回到ThreadLocalMap的构造函数
(1)生成一个Entry (key-value)数组 叫table
(2)通过名称和下一行代码,我们知道 这是通过一个hashcode生成一个索引
(3)然后将key ,value 封装成一个 Entry实例,table[i]指向这个实例。
知道ThreadLocalMap,我们再回到get中,再来看看getMap
ThreadLocalMap map = getMap(t);
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
我们看到返回的是一个线程中的threadLocals,我们到线程中一探究竟
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
这个threadLocals就是我们开始分析的ThreadLocalMap,到这里我们大致知道了:thread中有一个ThreadLocalMap 它是一个key-value结构,key是我们定义的ThreadLocal,value就是我们设置的值,继续往下看:
if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } }
通过 thread中的ThreadLocalMap获取一个Entry ,参数是this,是谁在调用get方法?是通过我们定义的ThreadLocal变量调用,那么这个this就是我们的ThreadLocal。前面我们知道ThreadLocal就是key,那么现在传入的就是key,那么肯定就是为了获取value
/** * 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); }
getEntry中 通过key计算一个索引,然后根据table查出Entry,如果查出的Entry的key是我们传入的key,那么返回这个Entry。(else 先不看),再对照上面取得Entry中的value,返回,结束。
因为我们是一步一步分析的,到这里是不是有点混乱,那我们来梳理一下:
(1)Thread 里面有一个 ThreadLocalMap, ThreadLocalMap中一个Entry数据结构,它是一个key-value结构,key是ThreadLocal,value是我们需要存的值。由Entry构成了一个table数组,用于存放这些Entry,通过ThreadLocal去存取值,实际就是把自己做为key,把值放到thread中的ThreadLocalMap中,这样就能明白为什么这些数据是线程相关的了,应该这些数据就是存到线程里面,别的线程里面肯定没有呀,画了个图,方便大家理解:
set 方法
有了上面的基础,再来看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)首先获取线程里面的ThreadLocalMap
(2)如果map 为空,那么先创建,creat里面就是我们前面分析过的ThreadLocalMap的构造函数,就不再分析了。
(3)如果map不为空,那么就设置值,key就是当前的ThreadLocal(this)
/** * 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 计算得到一个索引值
(2)根据索引值可以得到table 中的Entry
(3)按照指定的条件遍历table,nextIndex从字面意思可以看出是计算下一个索引值(这就是所谓的指定的条件),刚开始table[i]肯定是null的,所以暂时不看那段for循环
(4)将key,value 封装成Entry,将table[i]指向Entry。
ok,set方法我们也大致了解了,基本对ThreadLocal有了比较清楚的认识了,至少是揭开了它神秘的面纱。
ThreadLocal 源码深入分析
前面我们大致看了一下set 和 get 方法,了解了ThreadLocal的原理,但是还有一些比较细节的地方还没有仔细琢磨,在前面我们看到在get或者set的时候,都会通过key来计算一下索引(table的下标),在回想一下,table是一个数组,存取数据是通过下标来进行的,而这个下标是通过key来计算得到,这个很明显就是一种hash的计算,当然通过名称(key.threadLocalHashCode)我们也很容易的猜到是这样的,在ThreadLocal 类开始处,我们可以看到如下代码/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
可以看到一个静态的原子变量nextHashCode ,在nextHashCode()方法,不断的增加这个原子变量的计数,而这个增量就是HASH_INCREMENT,从此我们可以知道,每当实例一个ThreadLocal,那么其threadLocalHashCode 都在其原来的值上不断增加 HASH_INCREMENT。
在get 方法中的getEntry:
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); }
在计算索引的时候 用到了ThreadLocal实例的threadLocalHashCode ,然后和(table.length-1)相与,那么得到一个在0-table.length-1的数,这个数就用来存数据,所有取数据的时候直接用相同的方法得到索引就可以了。
当然知道hash的都肯定知道hash碰撞这个词,如果在索引i对应的位置已经存过值了,那么就该重新计算另一个位置索引(这个后面再细谈)
如果在计算的索引中不存在指定的ThreadLocal,那么说明可能发生了碰撞,也可能没有个ThreadLocal,那么执行getEntryAfterMiss 方法
/** * 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; }
如果取出的ThreadLocal不为空,但是不是我们指定的ThreadLocal,那么说明发生了碰撞,需要找下一个索引(再hash)
/** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
再次hash的方法也很简单,也就是线性递增,依次往后找就可以了,那么说明存数据的时候也是这种方法(很显然嘛,存取数据的hash算法一定要一致)
如果取出的k(ThreadLocal)为空(这里Entry 不为空),那么说明Entry 关联的这个key(ThreadLocal)被回收了(在程序中我们也许不在引用某个ThreadLocal实例,那么后面可能就会被gc回收掉 ),这个时候需要清理hash表(table),执行 expungeStaleEntry
/** * 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; }
(1)将table中的索引位置的数据清楚掉
(2)重新hash,为什么要重新hash,因为虽然索引i这个位置的数据没有了,但是在i位置上发生了hash碰撞的Entry数据是通过i再次hash计算得到下一个位置的值,既然i的位置数据都没有了,那么那些Entry有什么意义呢(同时table length的大小变化了,所以必须重新hash),所以需要为它们重新计算需要存放的位置。
所以才有了后面的循环,获得不断获得下个索引值,如果该位置关联的ThreadLocal为空,那么清除数据,否则根据当前的length和 ThreadLocal 重新计算存储的位置,如果重新计算出的位置有数据,那么再hash,依次进行,知道找到位置可以放为止。
最后 ,回到get 方法,如果table中get不到值,那么就会执行:
return setInitialValue();
这里面很简单 就是设置一个初始值,而这个值通过:
protected T initialValue() { return null; }
这个方法得到,因此我们可以重写该方法,设置我们自己需要的初始值。
看完get方法 ,我们再来回顾一下ThreadLocalmap里面的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; 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(); }
再开始,我们没有分析循环,因为第一次放入值,肯定不会执行循环。当通过计算出来的索引值获取Entry 不为空时,那么就执行循环
(1)如果取出的Entry就是我们要的数据,那么很好,结束
(2)如果取出的Entry 的key(ThreadLocal)为空,这就麻烦了,说明该Entry关联的ThreadLocal 被回收了,又需要整理table 了,来看看replaceStaleEntry
/** * Replace a stale entry encountered during a set operation * with an entry for the specified key. The value passed in * the value parameter is stored in the entry, whether or not * an entry already exists for the specified key. * * As a side effect, this method expunges all stale entries in the * "run" containing the stale entry. (A run is a sequence of entries * between two null slots.) * * @param key the key * @param value the value to be associated with key * @param staleSlot index of the first stale entry encountered while * searching for key. */ 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; 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的key为空了,那么其前一个Entry 的key 是不是也可能为空了呢(这里的前一个并不是i-1的那个,而是基于hash算法得出的前一个,这个很简单,如果由i算i+1的那么就逆推就好了),当然可能
(1)从此Entry往前找失效的Entry,如果找到了就用slotToExpunge标记
(2)从此Entry(取名 a)开始往后找,如果找到了一个key 和我们当前需要设置数据的key相同,那么说明有这个Entry(取名b),我们直接重新改它的值就好(e.value = value;),因为我们在找需要的key是,发现a的key为空,那么我们就把b和a交换,那么b的查找路径就会变短,接下来我们就需要整理 Entry 中key为空的节点了,如果前面有key为空的那么就从前面开始,否则从当前位置开始,expungeStaleEntry 前面讲过,里面主要就是搬数据(重新hash),cleanSomeSlots 里面比较简单,就是清理Entry,里面调用了expungeStaleEntry。
(3)如果在前面的查找并整理table中没有找到 我们要设置数据的 ThreadLocal,那么就需要构造一个新的Entry了
remove 方法
之所以把remove 放到这里讲,其实remove就是清理table,这个恰好在前面就分析过了public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) 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; } } }
简单贴一下源码就可以了,过程就不再分析了.
ok 分析完了,完了。
相关文章推荐
- Java并发编程之ThreadLocal源码分析
- Java并发包源码学习之线程池(一)ThreadPoolExecutor源码分析
- 并发编程4:Java 阻塞队列源码分析(上)
- 【Java8源码分析】并发包-CyclicBarrier
- Java并发之Semaphore的源码分析
- Java并发之线程池ThreadPoolExecutor源码分析学习
- java并发编程之源码分析ThreadPoolExecutor线程池实现原理
- Java concurrent Framework并发容器之ArrayBlockingQueue(1.6)源码分析
- Java concurrent Framework并发容器之concurrent.atomic包源码分析
- java 并发编程实战 第五天 ThreadPoolExecutor 源码分析
- java并发锁ReentrantLock源码分析一 可重入支持中断锁的实现原理
- java 并发 ConcurrentHashMap 与 HashTable源码分析总结
- java-----ThreadLocal源码分析
- 【Java8源码分析】并发包-ConcurrentHashMap(一)
- 【Java8源码分析】并发包-CopyOnWriteArrayList
- Java源码分析 - ThreadLocal
- Java并发----ConcurrentHashMap02--源码分析
- Java concurrent Framework并发容器之ConcurrentHashMap(Doug Lea 非JDK版)源码分析
- Java 并发框架 Disruptor 源码分析:RingBuffer
- Java并发系列之CountDownLatch源码分析