ThreadLocal的一些自己的理解

来源:互联网 发布:h5切水果游戏源码 编辑:程序博客网 时间:2024/05/22 02:51



有关于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);    }


我们进map的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);//对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;}@Overridepublic 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 blocke.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();}};




0 0