ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的成员变量都是线程不安全的。因为他可能被多个线程同时修改,此变量对于多个线程之间彼此并不独立,是共享变量。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程无法访问和修改。也就是说:将线程公有化变成线程私有化(空间换时间)。
注意:ThreadLocal通常都定义为static,ThreadLocal没有存储功能,变量副本的真实存储位置是Thread对象的threadLocals这个ThreadLocal.ThreadLocalMap变量中,可以将ThreadLocal理解为一个工具类,用来保证线程本地变量的存储和存储碰撞。
- public static void main(String[] args){
- ThreadLocal
threadLocal = new ThreadLocal(); - threadLocal.set("haha");
- System.out.println(threadLocal.get());
- }
创建一个和当前线程强相关的容器,把值放到这个容器里,线程之间容器相互隔离。
- /**
- * Sets the current thread's copy of this thread-local variable
- * to the specified value. Most subclasses will have no need to
- * override this method, relying solely on the {@link #initialValue}
- * method to set the values of thread-locals.
- *
- * @param value the value to be stored in the current thread's copy of
- * this thread-local.
- */
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- /**
- * Create the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
- *
- * @param t the current thread
- * @param firstValue value for the initial entry of the map
- */
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
此段代码可以看出,我们在set的时候,是获取了一个ThreadLocalMap对象(上述当中的容器),它是当前线程的一个参数,如果已经建立了,以threadlocal为key,直接往里面塞值,没有建立的话需要初始化一下。
- /**
- * Returns the value in the current thread's copy of this
- * thread-local variable. If the variable has no value for the
- * current thread, it is first initialized to the value returned
- * by an invocation of the {@link #initialValue} method.
- *
- * @return the current thread's value of this thread-local
- */
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
- }
- /**
- * Variant of set() to establish initialValue. Used instead
- * of set() in case user has overridden the set() method.
- *
- * @return the initial value
- */
- 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;
- }
获取线程中ThreadLocalMap已经存储的内容,如果ThreadLocalMap还没创建,则创建map并赋null。
- /**
- * Removes the current thread's value for this thread-local
- * variable. If this thread-local variable is subsequently
- * {@linkplain #get read} by the current thread, its value will be
- * reinitialized by invoking its {@link #initialValue} method,
- * unless its value is {@linkplain #set set} by the current thread
- * in the interim. This may result in multiple invocations of the
- * {@code initialValue} method in the current thread.
- *
- * @since 1.5
- */
- 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;
- }
- }
- }
remove就是将存储的变量清空。
- /**
- * Set the value associated with key.
- *
- * @param key the thread local object
- * @param value the value to be set
- */
- 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[] 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();
- }
先用hash码与长度取与,然后找到对应数组位置index,如果没有发生碰撞,就按指定位置存储,如果发生碰撞,就顺序找个空的位置存储。
- /**
- * Get the entry associated with key. This method
- * itself handles only the fast path: a direct hit of existing
- * key. It otherwise relays to getEntryAfterMiss. This is
- * designed to maximize performance for direct hits, in part
- * by making this method readily inlinable.
- *
- * @param key the thread local object
- * @return the entry associated with key, or null if no such
- */
- private Entry getEntry(ThreadLocal> key) {
- int i = key.threadLocalHashCode & (table.length - 1);
- Entry e = table[i];
- if (e != null && e.get() == key)
- return e;
- else
- return getEntryAfterMiss(key, i, e);
- }
-
- /**
- * Version of getEntry method for use when key is not found in
- * its direct hash slot.
- *
- * @param key the thread local object
- * @param i the table index for key's hash code
- * @param e the entry at table[i]
- * @return the entry associated with key, or null if no such
- */
- 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;
- }
查找和放入是一样的道理。
- /**
- * 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;
- }
- }
- }
看完代码,我们总结一下ThreadLocal它源码的核心几个方法及其作用

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。
通常ThreadLocalMap的生命周期跟Thread(注意线程池中的Thread)一样长,如果没有手动删除对应key(线程使用结束归还给线程池了,其中的KV不再被使用但又不会GC回收,可认为是内存泄漏),一定会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,Java8已经做了上面的代码优化。
1.线程安全的时间工具类
- package com.test;
-
- import java.text.DateFormat;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
-
- public class DateUtil {
-
- private static final String date_format = "yyyy-MM-dd HH:mm:ss";
-
- private static ThreadLocal
threadLocal = new ThreadLocal(); -
- public static DateFormat getDateFormat(String dateFormate) {
- DateFormat df = threadLocal.get();
- if(df == null){
- df = new SimpleDateFormat(dateFormate);
- threadLocal.set(df);
- }
- return df;
- }
-
- public static String formatDate(Date date,String dateFormate) throws ParseException {
- return getDateFormat(dateFormate).format(date);
- }
-
- public static Date parse(String strDate,String dateFormate) throws ParseException {
- return getDateFormat(dateFormate).parse(strDate);
- }
-
- }
2.B端存储用户信息
- public class UserThreadLocal {
- private UserThreadLocal(){}
-
- private static final ThreadLocal
LOCAL = new ThreadLocal<>(); -
- public static void put(SysUser sysUser){
- LOCAL.set(sysUser);
- }
- public static SysUser get(){
- return LOCAL.get();
- }
-
- public static void remove(){
- LOCAL.remove();
- }
- }