ThreadLocal源码解析

使用示例

public class ThreadLocalDemo {
    private static ThreadLocal threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(()->{
            threadLocal.set(1);
            System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
        },"thread1").start();
        new Thread(()->{
            threadLocal.set(2);
            System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
        },"thread2").start();
        new Thread(()->{
            threadLocal.set(3);
            System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
        },"thread3").start();
    }
}

执行结果

thread1,1
thread2,2
thread3,3

每个线程都有各自的变量副本,线程之间操作相互隔离,对其他线程不影响。

类结构图

ThreadLocal类继承关系

ThreadLocal类结构

源码分析

set方法

public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals,下面详细讲解
        ThreadLocalMap map = getMap(t);
        //如果map不为null,设置map,key为当前线程,value是传入的参数值
        if (map != null)
            map.set(this, value);
        //否则,创建一个map,key为当前线程,value是传入的参数值
        else
            createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Thread类有一个ThreadLocalMap类型的threadLocals变量,这个ThreadLocalMap是ThreadLocal的内部类

//Thread类
ThreadLocal.ThreadLocalMap threadLocals = null;
//ThreadLocal类的内部类ThreadLocalMap 
static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        //Entry内部类
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;
            //Entry的key是ThreadLocal类型的弱引用类型,value是Object类型
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
  }
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数组
            Entry[] tab = table;
            //数组长度
            int len = tab.length;
            //计算key的索引位置
            int i = key.threadLocalHashCode & (len-1);
            
            //遍历数组,如果不为空且不等于当前key,则继续往后面找下一个位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //获取数组当前位置的key
                ThreadLocal k = e.get();
              
                //如果key相等,则将该位置的Entry对应的value设置为传入的value值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果key为null,则替换当前位置的Entry
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //整个数组每个Entry都不为null,且key都不相等,则新建一个Entry
            tab[i] = new Entry(key, value);
            //数组长度+1
            int sz = ++size;
            //清理slot,如果slot都是有效的,且数组长度大于等于阈值,则2倍扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                //扩容
                rehash();
}
private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                //((i + 1 < len) ? i + 1 : 0);
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    //从当前位置开始往后遍历一段,对无效Entry进行清理
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
}
private static int nextIndex(int i, int len) {
      return ((i + 1 < len) ? i + 1 : 0);
}
private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
}
ThreadLocal类

ThreadLocal放入元素时,先计算下标,

  1. 如果该位置没有元素,直接放进去;
  2. 如果已经有元素,且key相等,则直接覆盖value;
  3. 如果已经有元素,且key不相等,则往后遍历一个空的位置放进去,且对无效的key进行清理


    ThreadLocal放入元素

get方法

public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //threadLocals不为null
        if (map != null) {
            //获取当前线程的ThreadLocal的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取Entry的value
                T result = (T)e.value;
                return result;
            }
        }
        //如果map为null,则初始化一个map,key为当前线程的threadLocal,value为对应类型的初始值
        return setInitialValue();
}
private Entry getEntry(ThreadLocal key) {
            //与存放时的逻辑一样,获取下标
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //如果该位置不为null,且key相当,直接返回该位置Entry元素,否则,从当前位置开始往后遍历
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
          
            while (e != null) {
                //获取当前元素的key
                ThreadLocal k = e.get();
                //如果key相等,返回该元素
                if (k == key)
                    return e;
                //如果key为null,清理无效的Entry
                if (k == null)
                    expungeStaleEntry(i);
                else
                    //继续往后找下一个位置
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
}
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}

key为什么要设计成弱引用

弱引用的作用是,只要GC,就会被回收,而不管内存是否足够。

因为线程Thread内部有一个threadLocals变量,而threadLocals变量属于ThreadLocalMap类型,ThreadLocalMap是Entry数组,key为ThreadLocal弱引用,如果Entry的key设计成强引用,在释放对ThreadLocal引用时,Entry还保持着对ThreadLocal的强引用,进而导致ThreadLocal对象不会被释放,只有在线程销毁的时候才会被释放,然而在线程池中的线程都是复用的,那么也就不会被释放了。而设计成弱引用,当发生GC时,这些弱引用的key就会被清除,但是这里需要注意,只是回收key,value并没有回收,所以可能出现内存泄漏。
为了解决内存泄漏问题,每次使用完以后,需要手动remove,就把这个Entry从ThreadLocalMap中清除掉了。

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}
/**
   * Remove the entry for key.
   */
private void remove(ThreadLocal key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
}

InheritableThreadLocal

ThreadLocal用来实现线程间的隔离,如果要实现父线程将数据传递到子线程,可以使用InheritableThreadLocal。

public class ThreadLocalDemo {
      private static ThreadLocal threadLocal = new InheritableThreadLocal<>();
      public static void main(String[] args) {
          threadLocal.set(1);
          new Thread(()->{
               System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
          },"thread1").start();
}

执行结果

thread1,1

main线程将值传递到了thread1线程。

public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        //重点在这里,如果父线程的inheritableThreadLocals不为空,则把父线程的inheritableThreadLocals作为参数为当前线程创建一个新的ThreadLocalMap,
        //以此实现父线程的数据传递到子线程
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

使用场景

SimpleDateFormat

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
            new Thread(()->{
                String date = sdf.format(new Date());
                try {
                    Date parseDate = sdf.parse(date);
                    String dateString2 = sdf.format(parseDate);
                    System.out.println(date.equals(dateString2));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
           },"thread"+i).start();
        }
}

执行结果

false
false
false
true
Exception in thread "thread2" Exception in thread "thread0" Exception in thread "thread1" Exception in thread "thread3" Exception in thread "thread4" Exception in thread "thread9" java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:601)
    at java.lang.Long.parseLong(Long.java:631)
    at java.text.DigitList.getLong(DigitList.java:195)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at thread.ThreadLocalDemo.lambda$main$0(ThreadLocalDemo.java:39)
    at java.lang.Thread.run(Thread.java:745)

说明是线程不安全的,这里可以用ThreadLocal解决。

数据源

//动态设置数据源
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal dataDource = new ThreadLocal();
    
    public static String getDataSource() {
        return (String) dataDource.get();
    }

    public static void setDataSource(String dataSource) {
        dataDource.set(dataSource);
    }

    public static void clearDataSource() {
        dataDource.remove();
    }
    
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }
}

你可能感兴趣的:(ThreadLocal源码解析)