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里面设的值

  1. public static void main(String[] args) {
  2. ThreadLocal<String> threadLocal = new ThreadLocal<>();
  3. new Thread(()->{
  4. try{
  5. TimeUnit.SECONDS.sleep(2);
  6. }catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println(threadLocal.get());
  10. }).start();
  11. new Thread(()-> {
  12. try {
  13. TimeUnit.SECONDS.sleep(1);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. threadLocal.set(new String("123"));
  18. }).start();
  19. }

其线程安全的特点如何实现?

ThreadLocalMap

在Thread类里面,维护了一个 ThreadLocalMap 类型的成员变量 threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null

ThreadLocalMap是ThreadLocal的一个内部类,当调用ThreadLocal的set()方法时,其实是以当前ThreadLocal对象为key,传进来的值为value存入到当前线程ThreadLocalMap中(所以一个ThreadLocal只能为每个线程保存一个变量副本

set()

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. //获取当前线程的ThreadLocalMap属性
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. //以当前ThreadLocal对象为key将value放入到map中
  7. map.set(this, value);
  8. else
  9. //创建一个ThreadLocalMap并存放value
  10. createMap(t, value);
  11. }
  12. ----------
  13. void createMap(Thread t, T firstValue) {
  14. t.threadLocals = new ThreadLocalMap(this, firstValue);
  15. }

具体调用的是ThreadLocalMap的set()方法,往map里面添加元素

  1. private void set(ThreadLocal<?> key, Object value) {
  2. Entry[] tab = table;
  3. int len = tab.length;
  4. int i = key.threadLocalHashCode & (len-1);
  5. for (Entry e = tab[i];
  6. e != null;
  7. e = tab[i = nextIndex(i, len)]) {
  8. ThreadLocal<?> k = e.get();
  9. //若当前key的值与已经放到Entry数组中的key相等,则覆盖掉原来的值
  10. if (k == key) {
  11. e.value = value;
  12. return;
  13. }
  14. //若发现当前位置Entry的key为null,则把该Entry清掉,重新new一个Entry,并把key赋值为当前的key
  15. if (k == null) {
  16. replaceStaleEntry(key, value, i);
  17. return;
  18. }
  19. }
  20. //插入新的Rntry
  21. tab[i] = new Entry(key, value);
  22. int sz = ++size;
  23. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  24. rehash();
  25. }

微信截图_20210710000103.png

get()

当调用ThreadLocal的get()方法时,其实是从当前线程的ThreadLocalMap中获取数据,所以保证了数据是线程安全的

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. //获取当前线程的ThreadLocalMap
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null) {
  6. //获取以当前ThreadLocal对象为key的Entry
  7. ThreadLocalMap.Entry e = map.getEntry(this);
  8. if (e != null) {
  9. @SuppressWarnings("unchecked")
  10. T result = (T)e.value;
  11. return result;
  12. }
  13. }
  14. //当获取不要entry,Entry的key被置为null,重新将key赋值为当前ThreadLcoal,并把value置为null
  15. return setInitialValue();
  16. }
  17. --------------------------
  18. private Entry getEntry(ThreadLocal<?> key) {
  19. int i = key.threadLocalHashCode & (table.length - 1);
  20. Entry e = table[i];
  21. if (e != null && e.get() == key)
  22. return e;
  23. else
  24. return getEntryAfterMiss(key, i, e);
  25. }

ThreadLocal内存泄漏

Entry是ThreadLocalMap中的内部类,其继承了WeakReference>,即Entry的key属性对ThreadLocal对象是一个弱引用

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. Object value;
  3. Entry(ThreadLocal<?> k, Object v) {
  4. super(k);
  5. value = v;
  6. }
  7. }

为什么要使用弱引用
当我们不需要往ThreadLocal里面存放数据时,将指向ThreadLocal对象的变量置为null,此时因为Entry里的key还指向ThreadLocal对象,所以无法进行回收,造成内存泄漏
微信截图_20210710000845.png
而将key对ThreadLocal对象变为弱引用,进行GC时不管有没有引用都会自动回收该对象
微信截图_20210710001532.png

这样就解决了内存泄漏了吗
在GC后,key弱引用指向的ThreadLocal对象会被回收,key变为null,这样会导致value永远都访问不到,造成内存泄漏。为此在ThreadLcoalMap的set()、get()方法中会做处理,以set()方法中的replaceStaleEntry()为例:

  1. private void replaceStaleEntry(ThreadLocal<?> key, Object value,
  2. int staleSlot) {
  3. Entry[] tab = table;
  4. int len = tab.length;
  5. Entry e;
  6. int slotToExpunge = staleSlot;
  7. for (int i = prevIndex(staleSlot, len);
  8. (e = tab[i]) != null;
  9. i = prevIndex(i, len))
  10. if (e.get() == null)
  11. slotToExpunge = i;
  12. // Find either the key or trailing null slot of run, whichever
  13. // occurs first
  14. for (int i = nextIndex(staleSlot, len);
  15. (e = tab[i]) != null;
  16. i = nextIndex(i, len)) {
  17. ThreadLocal<?> k = e.get();
  18. if (k == key) {
  19. e.value = value;
  20. tab[i] = tab[staleSlot];
  21. tab[staleSlot] = e;
  22. // Start expunge at preceding stale entry if it exists
  23. if (slotToExpunge == staleSlot)
  24. slotToExpunge = i;
  25. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  26. return;
  27. }
  28. if (k == null && slotToExpunge == staleSlot)
  29. slotToExpunge = i;
  30. }
  31. //如果key = null,重新创建一个Entry覆盖掉原来的Entry
  32. tab[staleSlot].value = null;
  33. tab[staleSlot] = new Entry(key, value);
  34. // If there are any other stale entries in run, expunge them
  35. if (slotToExpunge != staleSlot)
  36. cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
  37. }

不过还是建议我们使用完之后调用remove()方法清除,因为我们不知道线程下次调用set()和get()会是什么时候。养成良好的习惯,线程每次执行完后都要清理ThreadLocal。当线程被线程池管理时,如果不清理掉,该线程下次执行别的任务时再次使用ThreadLocal可能会使用旧值,造成一系列问题。