ThreadLocal源码分析
ThreadLocal是解决线程安全问题的一种方法,它通过为每个线程提供一个独立的变量副本避免了变量并发访问的冲突问题。一个ThreadLocal变量只与当前自身线程相关,对其他线程是隔离的。下面这段代码展示了ThreadLocal的使用。
public class test {private static final ThreadLocal<Object> tl = new ThreadLocal<>();public static void main(String[] args) {ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,1L, TimeUnit.MINUTES,new ArrayBlockingQueue<>(100));for (int i = 0; i < 5; i++) {MyRunnable runnable = new MyRunnable(i);poolExecutor.execute(runnable);}poolExecutor.shutdown();while (!poolExecutor.isTerminated()) {}System.out.println("Finished all threads");}public static class MyRunnable implements Runnable{private int id;public MyRunnable(int id){this.id=id;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"set()前:"+tl.get());tl.set(id);System.out.println(Thread.currentThread().getName()+"set()后:"+tl.get());try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
执行结果:
pool-1-thread-2set()前:null
pool-1-thread-5set()前:null
pool-1-thread-5set()后:4
pool-1-thread-1set()前:null
pool-1-thread-4set()前:null
pool-1-thread-3set()前:null
pool-1-thread-4set()后:3
pool-1-thread-1set()后:0
pool-1-thread-2set()后:1
pool-1-thread-3set()后:2
Finished all threads
这段代码中定义了5个线程对同一个ThreadLocal进行get和set操作,但是每个线程get到的值都是线程set后的值。可见,ThreadLocal做到了多线程的数据隔离。下面看看ThreadLocal的源码。先看ThreadLocal的set()方法。
public class ThreadLocal<T> {public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//将value添加到map,key是当前ThreadLocal的引用map.set(this, value);} else {createMap(t, value);}}ThreadLocalMap getMap(Thread t) {//每个线程都有一个名为threadLocals的ThreadLocalMap对象return t.threadLocals;}static class ThreadLocalMap {private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;//计算当前ThreadLocal在数组中的索引int i = key.threadLocalHashCode & (len-1);//找到一组具有相同hash的key//从这也能看出,ThreadLocalMap使用线性探测法解决哈希冲突for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//如果是同一个ThreadLocal,就直接覆盖原来的值if (k == key) {e.value = value;return;}//如果key是null,说明key被回收了,替换掉它if (k == null) {replaceStaleEntry(key, value, i);return;}}//如果key的hash地址是空的,那就直接插入tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)//扩容rehash();}}
}
根据ThreadLocal的源码,也能推断出ThreadLocal的数据结构了。
接下来在看一下get()方法:
public class ThreadLocal<T> {public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//取出当前ThreadLocal的键值对元素ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//返回当前ThreadLocal变量值T result = (T)e.value;return result;}}return setInitialValue();}private Entry getEntry(ThreadLocal<?> key) {//计算hash地址int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];//没有哈希冲突直接返回if (e != null && e.get() == key)return e;//发生了哈希冲突elsereturn getEntryAfterMiss(key, i, e);}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;if (k == null)expungeStaleEntry(i);else//线性探测下一个索引地址i = nextIndex(i, len);e = tab[i];}return null;}
}
ThreadLocal的内存泄露问题:
ThreadLocalMap中的key是ThreadLocal的弱引用,弱引用不会阻止垃圾回收器回收ThreadLocal实例,当ThreadLocal被回收后,value仍然存在,他们会占用内存,但是却无法通过ThreadLocal来访问,这就造成了内存泄露。因此,在使用完ThreadLocal变量后,可以及时remove掉这个ThreadLocal关联的键值对。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {m.remove(this);}
}