您的位置:首页 > 其它

赌十包辣条,你一定没见过这么通透的ThreadLocal讲解

2019-12-14 17:56 1261 查看

       不好意思,这次做个标题党,因为写这篇文章真的费了不少精力和心思,不希望随随便便被忽略,希望真的能帮到有需要的朋友!

       如果转载请声明,转自【https://www.cnblogs.com/andy-songwei/p/12040372.html】,谢谢!

       本文的主要内容为:

//=========源码5.3========
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;
}

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.
* ......
*/
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.
* ......
*/
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() {
......
}
}
View Code        这里面维护了一个Entry[] table数组,初始容量为16,当数据超过当前容量的2/3时,就开始扩容,容量增大一倍。每一个Entry的K为ThreadLocal对象,V为要存储的值。每一个Entry在数组中的位置,是根据其K(即ThreadLocal对象)的hashCode & (len - 1)来确定,如第44行所示,这里K的hashCode是系统给出的一个算法计算得到的。如果碰到K的hashCode值相同,即hash碰撞的场景,会采用尾插法形成链表。当对这个map进行set,get,remove操作的时候,也是通过K的hashCode来确定该Entry在table中的位置的,采用hashCode来查找数据,效率比较高。这也是HashMap底层实现的基本原理,如果研究过HashMap源码,这段代码就应该比较容易理解了。

       继续看源码5.1,第一次调用的时候,显然map应该是null,就要执行第8行createMap了,

//==========ThreadLocal=========源码5.4
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

       结合ThreadLocalMap源码第41行的构造方法,就清楚了这个方法创建了一个ThreadLocalMap对象,并存储了一个Entry<当前的ThreadLocal对象,value>。此时,在当前的线程下拥有了一个ThreadLocalMap,这个ThreadLocalMap中维护了一个容量为16的table,table中存储了一个以当前的ThreadLocal对象为K,value值为V的Entry。Thread、ThreadLocalMap、ThreadLocal、Entry之间的关系可以表示为下图:

 

 图5.1

       而如果当前Thread的map已经存在了,源码5.1就会执行第6行了,进而执行ThreadLocalMap中的set方法。结合前面对ThreadLocalMap的介绍,想必这个set方法也容易理解了,大致过程是:

    1)根据Thread找到map;

    2)通过传入的this(即ThreadLocal对象),得到hashCode;

    3)根据hashCode & (len - 1)确定对应Entry在table中的位置;

    4)如果该Entry存在,则替换Value,否则新建(ThreadLocalMap源码第78~92行表示在具有相同hashCode的Entry链表上找到对应的Entry,这和hash碰撞有关)。

 

       在调用ThreadLocal的get方法时又做了什么呢?看看其源码:

//=========ThreadLocal======源码5.5
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();
}

       现在,第12行及以前的代码应该很容易理解了,结合ThreadLocalMap中的get源码,我们再梳理一下:

    1)根据Thread找到自己的map;

    2)在map中通过this(即ThreadLocal对象)得到hashCode;

    3)通过hashCode & (len-1)找到对应Entry在table中的位置;

    4)返回Entry的value。

       而如果map为null,或者在map中找到的Entry为null,那么就执行第20行了。

//==========ThreadLocal========源码5.6
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;
}

protected T initialValue() {
return null;
}
第13行的initialValue()方法,前面介绍过,可以让子类重写,即给ThreadLocal指定初始值;如果没有重写,那返回值就是null。第4~9行前面也介绍过了,使用或者创建map来存入该值。

最后还一个remove()方法

//======ThreadLocal======
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

结合ThrealLocalMap中的remove方法,完成对ThreadLocal值的删除。其大致流程为:

    1)根据当前Thread找到其map;

    2)根据ThreadLocal对象得到hashCode;

    3)通过hashCode & (len -1)找到在table中的位置;

    4)在table中查找对应的Entry,如果存在则删除。

 

       总结:通过对提供的4个接口方法的分析,我们应该就能清楚了,ThreadLocal之所以能够为每一个线程维护一个副本,是因为每个线程都拥有一个map,这个map就是每个线程的专属空间。也就是存在下面的关系图(不用怀疑,该图和图5.1相比,只是少了容量大小):

结合这一节对ThreadLocal机制的介绍,实例3.1执行后的就存在如下的数据结构了:

 

 

6、ThreadLocal在Looper中的使用

       ThreadLocal在系统源码中有很多地方使用,最典型的地方就是Handler的Looper中了。这里结合Looper中的源码,来了解一下ThreadLocal在系统源码中的使用。

       我们知道,在一个App进程启动的时候,会在ActiivtyThread类的main方法,也就是App的入口方法中,会为主线程准备一个Looper,如下代码所示:

//======ActivityTread======源码6.1
public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
}

而在子线程中实例Handler的时候,总是需要显示调用Looper.prepare()方法来为当前线程生成一个Looper对象,以及通过Looper.myLooper()来得到自己线程的Looper来传递给Handler。

Looper中相关的关键源码如下:

//==========Looper========源码6.2

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;

/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself.  See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

/**
* Return the Looper object associated with the current thread.  Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

/** Initialize the current thread as a looper.
* ......
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

我们可以看到不少ThreadLocal的影子,Looper也正是通过ThreadLocal来为每个线程维护一份Looper实例的。通过我们前文的介绍,这里应该能够轻而易举理解其中的运作机制了吧,这里就再不啰嗦了。

 

7、实践是检验真理的唯一标准

        前面介绍了ThreadLocal提供的四个接口,以及详细讲解了它的工作原理。现在我们将实例3.1做一些修改,将各个接口的功能都包含进来,并稍微增加一点复杂度,如果能够看懂这个实例,就算是真的理解ThreadLocal了。

//=========实例7.1=======
private ThreadLocal<String> mStrThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
Log.i("threadlocaldemo", "initialValue");
return "initName";
}
};
private ThreadLocal<Long> mLongThreadLocal = new ThreadLocal<>();
private void testThreadLocal() throws InterruptedException {
mStrThreadLocal.set("main-thread");
mLongThreadLocal.set(Thread.currentThread().getId());
Log.i("threadlocaldemo", "result-1:name=" + mStrThreadLocal.get() + ";id=" + mLongThreadLocal.get());
Thread thread_1 = new Thread() {
@Override
public void run() {
super.run();
mStrThreadLocal.set("thread_1");
mLongThreadLocal.set(Thread.currentThread().getId());
Log.i("threadlocaldemo", "result-2:name=" + mStrThreadLocal.get() + ";id=" + mLongThreadLocal.get());
}
};
thread_1.start();
//该句表示thread_1执行完后才会继续执行
thread_1.join();
Thread thread_2 = new Thread() {
@Override
public void run() {
super.run();
Log.i("threadlocaldemo", "result-3:name=" + mStrThreadLocal.get() + ";id=" + mLongThreadLocal.get());
}
};
thread_2.start();
//该句表示thread_2执行完后才会继续执行
thread_2.join();
mStrThreadLocal.remove();
Log.i("threadlocaldemo", "result-4:name=" + mStrThreadLocal.get() + ";id=" + mLongThreadLocal.get());
}

在主线程中运行该方法,执行结果为:

12-14 16:25:40.662 4844-4844/com.example.demos I/threadlocaldemo: result-1:name=main-thread;id=2
12-14 16:25:40.668 4844-5351/com.example.demos I/threadlocaldemo: result-2:name=thread_1;id=926
12-14 16:25:40.669 4844-5353/com.example.demos I/threadlocaldemo: initialValue
12-14 16:25:40.669 4844-5353/com.example.demos I/threadlocaldemo: result-3:name=initName;id=null
12-14 16:25:40.669 4844-4844/com.example.demos I/threadlocaldemo: initialValue
12-14 16:25:40.669 4844-4844/com.example.demos I/threadlocaldemo: result-4:name=initName;id=2

此时存在的数据结构为:

       对于这份log和数据结构图,这里就不再一一讲解了,如果前面都看懂了,这些都是小菜一碟。

 

结语

       对ThreadLocal的讲解这里就结束了,能读到这里,也足以说明你是人才,一定前途无量,祝你好运,早日走上人生巅峰!

       由于经验和水平有限,有描述不当或不准确的地方,还请不吝赐教,谢谢!

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: