最近被问了ThreadLocal的原理,之前只知道他是用来做数据的线程私有化,具体怎么用以及他的原理并没有研究过,所以这次去研究一下他的原理。首先还是创建一个新的AS项目,看看这个ThreadLocal是怎么用的,然后再一层一层去剖析他(示例代码)
先编写一个线程类,在线程内用ThreadLocal处理一个在主存中的变量,然后循环操作的local变量;
- 错误示例代码片段 一
刚开始我写成了这个样 子,也出现了BUG(查看源代码 err1)
错误原因:
由于代码中的ThreadLocal 是子CustomThread的构造中初始化的,然而CustomThread的构造函数实在运行在UI线程,也就是这个ThreadLocal变量是属于UI线程的,然而在CustomThread的run方法是运行在行的线程里的,在run方法里获取UI线程的ThreadLocal 变量自然会返回一个空对象。
- 错误示例代码二
针对一上一个错误代码做了一些改进,代码如下(查看源码 err2)
虽然没有报错,但是看了一些log输出按我的理解不应该是这个结果,理想状态下各自线程对refObj的操作应该是隔离的,互不影响。静下心来,然后仔细回顾了一下ThreadLocal的概念...
ThreadLocal 所维护的变量是线程私有的,也就是说当ThreadLocal在一个全局的位置即各线程都可见的位置,分别在各自线程调用ThreadLocal.set(T)之后,ThreadLocal就为调用set的线程维护传入的这个变量T即set里的参数。ThreadLocal.get函数只能获取在本线程set的那个变量T。但是这个例子中ThreadLocal为各线程中维护的变量是同一个实例refObj,所以当在各自线程获取所维护的实例并调用其内部方法时,实际上就是在操作同一个对象,所以会受影响。用一段代码证明这件事(获取代码)
代码实例也就写完了,下面去研究一下内部原理.稍等片刻。
刚刚拜读了ThreadLocal 的源码,大概的原理如下:
Java 每个线程Thread都维护了一个ThreadLocalMap ,这个是真正线程私有的。ThreadLocal就是借助这个完成任务的。下面跟着代码走一下内部流程,
- ThreadLocal构造函数
/** * Creates a thread local variable. */ public ThreadLocal() { }
这个构造啥都没做,直接跳过
-
ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这个函数还算有点料,首先获取当前线程的Thread(话说忽然对这个Thread有了兴趣,下次研究一下Thread),然后在去获取这个Thread的ThreadLocalMap,即调用getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如果当前线程有ThreadLocalMap 就向Map添加value;如果没有就创建ThreadLocalMap并向创建后的Map添加Value;
直接看ThreadLocalMap的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();
}
代码很简单,就是ThreadLocalMap的entry数组里添加一个Value,但是要注意一下这个key.threadLocalHashCode
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);
}
它是由AtomicInteger原子操作类生成的值,这么做是为了保证在多线程环境下的唯一性,每一个ThreadLocal实例都会有一个唯一的threadLocalHashCode,这样的值也是最适合做为键值对中的Key了