一.简介
ThreadLocal 顾名思义 线程本地变量的意思。
适用于每个线程需要自己独立的实例且该实例需要在 当前线程的 多个方法 中被使用,也即变量在线程间隔离而在方法或类间共享的场景。切记 ThreadLocal 是某个变量被某个线程生命周期内多个方法共享。而不是多个Thread共享这个变量。
二.源码
设置值源码
set方法源码
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//然后根据当前线程 获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)//ThreadLocalMap对象不为空 ThreadLocalMap set值
map.set(this, value);
else//ThreadLocalMap对象为空 创建ThreadLocalMap对象
createMap(t, value);
}
首先,获取当前线程。然后根据当前线程 获取ThreadLocalMap对象。
获取ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
即 返回Thread类的threadLocals对象。那么这个对象是什么呢?
Thread类中
ThreadLocal.ThreadLocalMap threadLocals = null;
且Thread类中threadLocals 变量没有赋值的地方。也就是说这个值首次使用肯定是空。那么这个变量什么时候赋值呢?下面讲解。
因为首次获取的threadLocals对象为空,即ThreadLocalMap map为空。所以创建一个ThreadLocalMap。
创建ThreadLocalMap对象
/**
* 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);
}
也就是说创建了一个ThreadLocalMap对象。并将创建的ThreadLocalMap对象赋值给Thread类的变量。
ThreadLocal.ThreadLocalMap threadLocals = null;
那么ThreadLocalMap这个类结构是怎样的那?是不是和HashMap一样呢?下面看
ThreadLocalMap类部分源码
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* 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);
}
/**
* 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();
}
/**
* 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;
}
}
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
}
小结1
<1> ThreadLocalMap类是ThreadLocal的内部静态类。
<2> ThreadLocalMap默认大小是16。和HashMap差不多也存在扩容resize()方法。且每次扩容也是两倍。
<3> ThreadLocalMap类中有三个常用的方法
设置值
private void set(ThreadLocal<?> key, Object value)
@param key the thread local object 即 Key就是ThreadLocal类本身 使用的时候传入this。
@param value the value to be set 即 Value就是要设置的Object值。
获取值
private Entry getEntry(ThreadLocal<?> key)
key the thread local object 即 Key就是ThreadLocal类本身 使用的时候传入this。
清空值
private void remove(ThreadLocal<?> key) 即 Key就是ThreadLocal类本身 使用的时候传入this。
<4> ThreadLocalMap类中有一个内部静态类Entry(也就是ThreadLocalMap类中存储的Key-Value实体)是弱引用的。
static class Entry extends WeakReference<ThreadLocal<?>> {
}
继续
因为创建了t.threadLocals对象,所以下一次set的时候就直接 set值就可以了。
ThreadLocalMap map.set(this, value);
至此,设置值源码就讲解完了。下面看获取值。
获取值源码
get()方法源码
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();
}
首先,获取当前线程,然后根据当前线程 获取ThreadLocalMap对象。
如果map不为空。继续获取map的Entry对象。获取Entry对象时Key为this。即ThreadLocal本身。最后通过Entry实体类获取Entry实体类的value。
如果map为空,则执行setInitialValue方法,初始化ThreaLocalMap。
setInitialValue方法源码
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;
}
至此,获取值源码就讲解完了。下面看清空值。
清空值源码
remove()方法源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
首先,获取当前线程,然后根据当前线程 获取ThreadLocalMap对象。
然后执行map的remove()方法。传参this。即ThreadLocal本身。
总结
<1> ThreadLocal是存储 当前线程 变量的类。
<2> 内部有一个静态类ThreadLocalMap。ThreadLocalMap和HashMap类似。ThreadLocalMap类内部还有一个静态类Entry。Entry类是弱引用的。主要考虑因为你的Key其实是ThreadLocal本身,然后又和当前线程有关。可能会造成内存泄漏。
<3> ThreadLocal类本身提供三个方法。获取值+设置值+清空值。这三个方法都会首先获取当前线程,然后获取ThreadLocalMap对象。最后都是操作的ThreadLocalMap类的内部静态类Entry类。
<4> ThreadLocal类 是在一个线程中提供当前线程所有方法共享数据的。所以一般是静态的。比如Handler源码中操作Looper变量。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
<5> 使用ThreadLocal时,记住最好是在不用的地方清空一下。因为可能造成内存泄漏,且sThreadLocal 一般是静态的也需要置空。
三.ThreadLocal内存泄漏
上述源码已经分析过,无论是set还是get值。其实都是操作的ThreadLocalMap类的内部静态类Entry类。因为Entry类是弱引用的。
所以 ThreadLocalMap类 和 其内部静态类Entry类。之间是弱引用的关系。
但是 ThreadLocalMap类 和 ThreadLocal类 之间是强引用的。因为。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
所以,如果当前线程在执行一个任务后,就销毁了的话是没有问题的。但是如果是线程池中的操作的话。可能就会有内存泄漏的问题。
所以,我们在使用ThreadLocal时,最后在适当的时机,调用ThreadLocal的remove()方法。
Entry中对ThreadLocal使用弱引用,但value是强引用。其实value作为强引用设计合理,如果使用软或弱引用,就出大问题了,程序跑着跑着,突然get到了一个null。就不好了。
四.ThreadLocal使用举例、
1.代码
public class ThreadLocalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_threadlocal);
ThreadLocal<String> threadLocal = new ThreadLocal<>();
new Thread(() -> {
threadLocal.set("线程A的变量 001");
Log.d("ThreadLocalActivity", "线程A取值:" + threadLocal.get());
}).start();
new Thread(() -> {
threadLocal.set("线程B的变量 002");
Log.d("ThreadLocalActivity", "线程B取值:" + threadLocal.get());
}).start();
}
}
2.结果
D/ThreadLocalActivity: 线程A取值:线程A的变量 001
D/ThreadLocalActivity: 线程B取值:线程B的变量 002
3.总结
因为ThreadLocal内部使用的是ThreadLocalMap。而通过源码分析,这个Map的Key就是线程本身。所以在不同的线程set值。取值时不会错乱。即线程的隔离。