Threadlocal是一个线程内部的存储类,存储在里面的变量是线程安全的,数据存储以后,只有指定线程可以得到存储数据。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread
下面代码中,上面的线程拿不到下面线程中往threadlocal里面设的值
public static void main(String[] args) {ThreadLocal<String> threadLocal = new ThreadLocal<>();new Thread(()->{try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadLocal.get());}).start();new Thread(()-> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}threadLocal.set(new String("123"));}).start();}
其线程安全的特点如何实现?
ThreadLocalMap
在Thread类里面,维护了一个 ThreadLocalMap 类型的成员变量 threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null
ThreadLocalMap是ThreadLocal的一个内部类,当调用ThreadLocal的set()方法时,其实是以当前ThreadLocal对象为key,传进来的值为value存入到当前线程ThreadLocalMap中(所以一个ThreadLocal只能为每个线程保存一个变量副本)
set()
public void set(T value) {Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap属性ThreadLocalMap map = getMap(t);if (map != null)//以当前ThreadLocal对象为key将value放入到map中map.set(this, value);else//创建一个ThreadLocalMap并存放valuecreateMap(t, value);}----------void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
具体调用的是ThreadLocalMap的set()方法,往map里面添加元素
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();//若当前key的值与已经放到Entry数组中的key相等,则覆盖掉原来的值if (k == key) {e.value = value;return;}//若发现当前位置Entry的key为null,则把该Entry清掉,重新new一个Entry,并把key赋值为当前的keyif (k == null) {replaceStaleEntry(key, value, i);return;}}//插入新的Rntrytab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}

get()
当调用ThreadLocal的get()方法时,其实是从当前线程的ThreadLocalMap中获取数据,所以保证了数据是线程安全的
public T get() {Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//获取以当前ThreadLocal对象为key的EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//当获取不要entry,Entry的key被置为null,重新将key赋值为当前ThreadLcoal,并把value置为nullreturn setInitialValue();}--------------------------private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}
ThreadLocal内存泄漏
Entry是ThreadLocalMap中的内部类,其继承了WeakReference
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
为什么要使用弱引用?
当我们不需要往ThreadLocal里面存放数据时,将指向ThreadLocal对象的变量置为null,此时因为Entry里的key还指向ThreadLocal对象,所以无法进行回收,造成内存泄漏
而将key对ThreadLocal对象变为弱引用,进行GC时不管有没有引用都会自动回收该对象
这样就解决了内存泄漏了吗?
在GC后,key弱引用指向的ThreadLocal对象会被回收,key变为null,这样会导致value永远都访问不到,造成内存泄漏。为此在ThreadLcoalMap的set()、get()方法中会做处理,以set()方法中的replaceStaleEntry()为例:
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;int slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;// Find either the key or trailing null slot of run, whichever// occurs firstfor (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;// Start expunge at preceding stale entry if it existsif (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}//如果key = null,重新创建一个Entry覆盖掉原来的Entrytab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// If there are any other stale entries in run, expunge themif (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}
不过还是建议我们使用完之后调用remove()方法清除,因为我们不知道线程下次调用set()和get()会是什么时候。养成良好的习惯,线程每次执行完后都要清理ThreadLocal。当线程被线程池管理时,如果不清理掉,该线程下次执行别的任务时再次使用ThreadLocal可能会使用旧值,造成一系列问题。
