有关于ThreadLocal的原理的源代码解释在下面已经说的很清楚了 源代码也很清晰易懂
http://www.cnblogs.com/dolphin0520/p/3920407.html
个人对于ThreadLocal类的一些总结:
1.
ThreadLocal类并不是将线程和所存储的对象进行键值保存的和进行取值的(这是我第一反应以为的)
而是将存储的值保存到对应Thread对象的ThreadLocalMap里,ThreadLocalMap的实现原理类似于hashMap,
内部有一个Entry数组,一个Entry通常至少包括key,value, 查找时通过一定的运算规则运算Key的HASH值,来得到Entry在数组中的位置,进而得到相应的value。
但是这个ThreadLocalMap是将当前ThreadLocal对象实例传入当作键值,将set的对象当value值进行存储
下面是ThreadLocal的set方法源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//将this传入
else
createMap(t, value);
}
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);//对key进行hash运算 存储
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();
}
取值时也根据对应的thread对象拿出自己的threadLocalMap,再根据threadLocal的实例为键值得到对应本线程本ThreadLocal的副本
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
2
每个threadLocalMap对应每个线程只能存储一个对象
也就是说你set了一个对象A,再放入对象B ,对象B就会把对象A取代 这就跟HashMap一样
因为ThreadLocal是键值.。通过以上的代码也能看出来
3.
先看一段代码
public class Test
{
public int i = 1;
static ThreadLocal<Object> t = new ThreadLocal<Object>();
public static void main(String[] args){
Test test = new Test();
Test test1 = new Test();
MyThread m1= new MyThread(2,test,true);
// MyThread m2 = new MyThread(3,test1,false);
MyThread m2 = new MyThread(3,test,false);
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
int obj;
boolean flag;
Test t;
public MyThread(int i,Test t ,boolean flag){
this.obj = i;
this.t=t;
this.flag=flag;
}
@Override
public void run()
{
Test.t.set(obj);//该放入的对象被后面的所取代
Test.t.set(t);//只在run期间threadLocals是保存的 run之后threadLocals置空
//并不是由threadLocal给你克隆 副本 而是由你自己来设置放进去的副本
Test t = (Test) get();
if(flag==false){
try
{
Thread.sleep(5000);//睡眠 确保另外一个线程已经操作结束
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("线程为"+Thread.currentThread().getId()+"原来的:"+t.i);
t.i = obj;//改变值
System.out.println("线程为"+Thread.currentThread().getId()+"现在的:"+((Test)get()).i);
System.out.println("线程为"+Thread.currentThread().getId()+"所存储的对象:"+get());
}
public Object get(){
return Test.t.get();
}
}
此时输出如下:
线程为9原来的:1
线程为9现在的:2
线程为9所存储的对象:ThreadLocalTest.Test@65b8b5cd
线程为10原来的:2
线程为10现在的:3
线程为10所存储的对象:ThreadLocalTest.Test@65b8b5cd
我在两个线程放入了同一个Test对象test,当然run方法里也set了这个test
像我刚开始以为ThreadLocal会自动为每个线程创建一个副本(好可笑的梦哈)
这段代码也证实了并不会。
所以并不是由ThreadLocal给你克隆副本,而是由你自己来设置放进去的副本
如果将注释的代码代替
则输出如下:
线程为9原来的:1
线程为9现在的:2
线程为9所存储的对象:ThreadLocalTest.Test@65b8b5cd
线程为10原来的:1
线程为10现在的:3
线程为10所存储的对象:ThreadLocalTest.Test@72d2ee5d
上面啰嗦一大堆 就是为了让我自己认识到ThreadLocal需要自己来设置对应每个线程的副本
而这就需要执行ThreadLocal的set方法 或者重写ThreadLocal的initialValue方法
如同下面这样:
static ThreadLocal<Object> t = new ThreadLocal<Object>(){
public Object initialValue(){
return new Test();
}
};