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);elsecreateMap(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);elsecreateMap(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;elsereturn 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);elsei = 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();}
}