写这篇文章不在计划之内, 主要是分析到Handler消息机制一文中,牵涉到ThreadLocal内容,一次全部写完文章过长,所以打算单独摘出来梳理成为一篇文章。
在我们日常开发中用到ThreadLocal的地方很多,在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。
这里最主要的部分就是ThreaLcoal内部维护了一个HashMap, 下边我们看一下最主要的类ThreadLocalMap
重点 敲黑板 来一起看源码。
/**
* 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) {
/// 可以看到在ThreadLocal创建的时候, 是获取到了当前线程t, 然后获取线程t的本地存储ThreadLcoalMap,然后对map进行操作。
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
/// 当然如果当前线程还没有创建过ThreadLocalMap,则创建Map
createMap(t, value); /// 创建Map 的过程 看下边ThreadLocalMap 中方法。
}
接下来看一下另一个重要方法,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);
/// 同样是获取到了线程内部的引用 map对象, 通过map内部getEntry方法 获取到该ThreadLocal对应的对象Entry
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果没有, 默认为null, 所以在没有set 而且没有重写initialValue方法的话,获取到的值就是null。
return 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
/// 如果还没有该map ,则使用当前初始值来创建。默认为null
createMap(t, value);
return value;
}
/**
* 返回为当前线程创建的初始化值使用, 一般在get方法中调用和 remove方法后get调用,
* 如果使用可以通过内部类继承然后重写该方法即可。
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
* 获取到Thread的内部引用对象 threadLcoals
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 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)
/// 这个方法就是通过map 的内部方法 remove 移除当前的值。
m.remove(this);
}
说起ThreadLocal 与Thread的关系,借用Java编程思想中对线程本地存储的定义, 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。 简单理解一下就是Thread上的LocalVariables了, 既然如此就进入Thread源码看一下。 很明显就找到了相关引用。
/* Thread中引用了 ThreadLocal.ThreadLocalMap 很明显一线程中可以放置很多变量 , 这个Map可以在ThreadLocal中维护 */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal 可继承的ThreadLcoal
这个Map可以在ThreadLocal中维护
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
主要用于在创建线程时继承当前线程的ThreadLocal values , 可以在Thread 的init中调用, 一般情况下,如果父类的可继承的ThreadLcoal.ThreadLcoalMap 指定过,并且不为空 则默认创建的子线程继承该变量。 具体创建方法就是ThreadLocal中的createInheritedMap方法。
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
看一下ThreadLcoal中的createInheritedMap
/**
* Factory method to create map of inherited thread locals.
* Designed to be called only from Thread constructor.
*
* 仅仅只有在Thread 构造函数中调用
* @param parentMap the map associated with parent thread
* @return a map containing the parent's inheritable bindings
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
ThreadLocalMap 是ThreadLocal 的内部类, 既然起名为Map 则肯定和Map有相似之处,下边从get ,set ,remove方法来看。
这是ThreadLcoalMap 的基本结构, 继承自弱引用WeakReference 使用了泛型ThreadLcoal
/**
* 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;
}
}
这里几个方法是创建Map的过程, 列举出调用和最简单的一个构造函数。
/**
* 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);
}
/**
* 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 , Entry 为item ,初始化INITIAL_CAPACITY 默认为16个
table = new Entry[INITIAL_CAPACITY];
/// 计算出第一个key的索引 i
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
/// 初始化 并设置size 值
table[i] = new Entry(firstKey, firstValue);
size = 1;
/// 同时 设置容器大小的临界值,并传入初始化大小。
setThreshold(INITIAL_CAPACITY);
}
/**
* 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.
* /// 通过Key的hash值来创建其对应的索引值,找到entry
*
* @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
/// 如果没有在直接的table线性表中找到的话。
return getEntryAfterMiss(key, i, e);
}
可以看出它考虑到了哈希碰撞的情况,因为在遍历set值的时候考虑到哈希碰撞的问题一个节点对应两个值,一般会取(key的hashcode值&table.lenth-1)获取一个数组的位置,将其放入到该节点的位置。这里相当是一个逆运算,省去了遍历的性能开销问题,直接取该节点上的值(可以参考我以前的文章HashMap原理解析)。当然还有getEntryAfterMiss方法是为了解决出现了hash碰撞的问题,以下是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;
/// 如果k为空的直接从链表中擦除,方便GC进行回收
if (k == null)
expungeStaleEntry(i);
else
/// 循环找到下一个index
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* 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;
//如果数组中没有冗余的null值并且如果size大于临界值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
/// 进行扩容 ,这里就不再进行解释如何扩容了。
rehash();
}
/**
* 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();
// 置空,以便GC回收
expungeStaleEntry(i);
return;
}
}
}
理解了上边所说的ThreadLocalMap 的引用以及get和set, 这里就引出了个问题,如何保证每个线程中引用ThreadLcoalMap中创建的ThreadLcoal是唯一,并且高效的存取就成了至关重要的问题。
看到上边的ThreadLcoalMap中的set方法 和get方法, 如果懂得HashMap存储结构的话, 会发现和HashMap中indexfor (key的Hash& table.length-1) 有的一拼, 但是这里用的Hash key生成器不是ThreadLcoal对象的Hash值, 而是从0开始的AtomicInteger 通过getAndAdd HASH_INCREMENT 来生成。
/**
* 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);
}
这样就保证了每一个 ThreadLocal 对象有一个创建时生成唯一的 Id。
看了这么多,ThreadLcoal结构终于清晰了。 可以这么理解,ThreadLcoal是Thread中持有的一个放置Thread独有变量的Hash表。这个Hash表又经过特殊散列优化,使实用效率更加高效。可以说Java工程师们为了提高程序的结构性和高效性操碎了心啊,对于我们普通使用者来说简单方便而且又浑然一体,的确牛气。