ThreadLocal
2012-12-14 17:10 ggzwtj 阅读(1854) 评论(2) 编辑 收藏 举报ThreadLocal和Thread的关系如下:
这个结构还是非常清楚的,但是看网上的一些代码感觉很晕,下面来看为什么会晕,比如(下面的代码是根据网上的代码重写的):
class MyThreadLocal extends ThreadLocal<Integer> { public Integer initialValue() { return 0; } public void incr() { Integer value = get(); if (value == null) { value = 0; } set(value + 1); } } class MyThread extends Thread { MyThreadLocal value = null; MyThread(MyThreadLocal value) { this.value = value; } public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread() + " " + value.get()); value.incr(); } } } public class A { public static void main(String[] args) { MyThreadLocal onlyOne = new MyThreadLocal(); Thread a = new MyThread(onlyOne); Thread b = new MyThread(onlyOne); a.start(); b.start(); } }
-----------------------------------
output is:
Thread[Thread-1,5,main] 0
Thread[Thread-1,5,main] 1
Thread[Thread-1,5,main] 2
Thread[Thread-0,5,main] 0
Thread[Thread-0,5,main] 1
Thread[Thread-0,5,main] 2
在代码的最后给出了程序的输出,可以两个线程虽然是用的同一个MyThreadLocal,但是丝毫没有相互影响,为什么?
- 首先,我们看到MyThreadLocal中没有保存任何的数据,那么相互之间有没有影响当然也就和MyThreadLocal本身没有关系了!
- 然后,再看ThreadLocal中,算的上属性的也只有threadLcalHashCode,显然这只是一个在创建的的时候设置好的常量,所以也没关系!
没有找到,最直接的看下get方法不就知道在哪里保存了,如下:
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(); }
那么可以看到,其实每次get的时候只是从Thread.threadLocals中去取值,而不管是MyThreadLocal还是ThreadLocal,只是对ThreadLocalMap的封装而已(所以被上面的代码绕晕了)。从代码ThreadLocalMap.Entry e = map.getEntry(this)可以知道,其实ThreadLocal是作为一个Key存在的,也就是外面看到的是Key,那么两个线程在能看到同一个ThreadLocal的时候并试图通过它来操作的时候会发现:
根据同一个ThreadLocal在不同的两个线程中get到的结果是不一样的(当然也有可能是一样)。而如果ThreadB错误地调用Thread.set(ObjectB)的时候,这只会在ThreadLocalMapB中保存一条垃圾数据,而不会影响到ThreadA。那么,如果我们自己不用ThreadLocal的话,怎么来实现类似的功能呢?
class MyThread extends Thread { private Map value = null; public void run() { // do someting } }
把线程要用到的数据保存在value中,那么其他的线程就访问不到了(也就线程安全了),但是如果在多考虑一些东西,其实写完和ThreadLocal和ThreadLocalMap的代码差不多了。好了,下面来看ThreadLocalMap的代码:
private void set(ThreadLocal key, Object value) { 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(); }
在set的时候有几种种情况:
- 从HashCode的位置开始遍历Entry,如果找到Key,那么该位置设置为Value,返回;
- 在遍历的过程中发现Key=null的Entry、并且后面有Key相等的Entry,那么就将这两个位置交换并设置value值;
- 如果发下Key=null的Entry,但是后面没有相等Key的Entry,那么就Value设置到这个Entry;
- 上面的情况都没有发生,最后落在了Entry=null的情况,此时创建一个Entry(这里相当于table变大了,所以要检查是否需要resize);
还有两个有意思的方法:
- cleanSomeSlots:本意是清楚一些废弃的Entry,但是其中的循环却只执行了Log(N)次,其实它是在寻找“效率”和“效果”之间的平衡点;
- expungeStaleEntry:清理垃圾和解决hash冲突,这样的话有助于让GC去回收内存;
有了垃圾清理的时候,如果ThreadLocal已经被释放了(被设置为NULL),那么就有一定的概率在普通的操作中将对应Entry设置为Null从而被回收,如果不回收的话是不是有可能产生内存泄露?
看到这里,其实ThreadLocal使得我们的线程能安全的访问一些变量(就算是我们把ThreadLocal暴露给了所有的线程),而能安全访问得益于ThreadLocalMap被保存在Thread类中,值得注意的地方是防止内存泄露,到这里就会发现其实ThreadLocal这个名字还是相当准确的。。。
---------------------------------------
欢迎拍砖。