使用示例
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
每个线程都有各自的变量副本,线程之间操作相互隔离,对其他线程不影响。
类结构图
源码分析
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放入元素时,先计算下标,
- 如果该位置没有元素,直接放进去;
- 如果已经有元素,且key相等,则直接覆盖value;
-
如果已经有元素,且key不相等,则往后遍历一个空的位置放进去,且对无效的key进行清理
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();
}
}