最近在回顾一下Handler的源码,自然而然延伸出多线程的一些问题,然后Looper就是主角了,细心的同学看源码肯定发现这个looper是存放在ThreadLocal中的,今天咱们就来分析一下它到底是一个什么东西。
- ThreadLocal的作用.
隔离多个线程私有的数据,防止多线程并发情况下自己线程的数据被其他线程读写。
ThreadLocal 的使用非常的简单。
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("测试ThreadLocal");
String result = threadLocal.get();
System.out.println("result = " + result);//result = 测试ThreadLocal
threadLocal.remove();
- set和get和remove方法
/**
* 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();//首先获取当前的线程,暂且理解为一个key
ThreadLocalMap map = getMap(t);//通过当前线程,获取ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* 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();//不存在的时候返回初始值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);
}
- 上面的代码比较容易理解,上面三个方法都用到了getMap方法
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;//ThreadLocalMap为Thread的成员变量。每个Thread都有自己的ThreadLocalMap。到此同学们应该知道为什么ThreadLocal为什么可以实现线程间数据隔离了,就是因为数据都存储在ThreadLocalMap中。
}
- ThreadLocalMap 从名字看它就是一个map,和大家熟悉的HashMap有些类似,但它有自己特点,咱们慢慢来分析它。
//ThreadLocal静态内部类
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.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
结构大概如下:
它其实和HashMap很像,但是它没有链表和红黑树接口,那么遇到hash冲突怎么解决呢?
/**
* 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) {//当前数据位置已存在,并且key相同直接替换value
e.value = value;
return;
}
if (k == null) {//当前数据位置为null,创建一个entry放进去
replaceStaleEntry(key, value, i);
return;
}
//这个循环还有一个隐藏的条件,如果当前位置数据已经存在,但是key不相等,则执行下一次循环 “e = tab[i = nextIndex(i, len)]”,继续查找下一个位置存放。
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//这个循环还有一个隐藏的条件,如果当前位置数据已经存在,但是key不相等,则执行下一次循环 “e = tab[i = nextIndex(i, len)]”,继续查找下一个位置存放。
- 共享数据 inheritableThreadLocals
//使用方式
private void inheritableThreadLocalTest() {
inheritableThreadLocal.set("mainThread value");//在mainThread存入值
new Thread("Thread~1") {
@Override
public void run() {
super.run();
//Thread~1 的parent是mainThread
String value = inheritableThreadLocal.get();
System.out.println("value = " + value + " ,currentThreadName: " + Thread.currentThread().getName());//value = mainThread value ,currentThreadName: Thread~1
}
}.start();
}
//源码
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.
*/
// 大家注意到Thread类中,除了threadLocals,还有一个成员变量inheritableThreadLocals,根据名字可猜测是可继承的threadlocals
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...省略部分代码
//1.这里复制(继承)了parent的inheritableThreadLocals,好奇parent是什么的小伙伴,看一下源码init中,Thread parent = currentThread();它是创建该线程的线程。
//2.如何给parent的inheritableThreadLocals赋值呢?它的权限是默认的,我们无法在我们的代码中引入,需要借助jdk封装好的InheritableThreadLocal,这个源码很简单就不带大家看了。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
- ThreadLocal中的内存泄漏问题
static class ThreadLocalMap {
//Entry是弱引用对象,ThreadLocal是被弱应用对象,它会被gc回收,同学们别搞混了
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
ThreadMap只有key是弱引用,value依然是强引用。
引用链如下:
Thread----ThreadLocalMap----Entry----value
当threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收,但是value依然不能回收(有一条强引用链),直到Thread销毁,value才能被回收。但是线程池中,线程不会销毁的情况下这种问题就无法解决了。
解决方案:调用remove方法。
/**
* 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;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;//将value主动置为null,以便于gc回收
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相对来说并不复杂,大家多看几遍源码大概能理清楚它的原理。第一次写文章,各位同学如果在阅读中发现问题,欢迎大家评论指正。 码字不易,喜欢的点个赞吧。谢谢大家!