在activeJDBC框架内部的实现中看到了 ThreadLocal 这个类,记录下了每个线程独有的连接
private static final ThreadLocal<HashMap<String, Connection>> connectionsTL = new ThreadLocal<>();
感觉是个知识点,就打开源码看看了。先看一下源码里的解释
这个鸟文,瞎翻译一下,就是:
先跑一下用法吧,
package com.test.threadlocal;
public class TestController {
private static int index = 0;
private static String str = "这个字符串是每个线程共享的";
// 这个变量,看似是一个类的静态属性,实则是每个线程有自己独有的区域
private static ThreadLocal<String> threadStr = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "main线程专享";
}
};
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 3; i++) {
Thread t = new MyThread();
t.start();
t.join();
}
System.out.println(str);
System.out.println(threadStr.get());
}
static class MyThread extends Thread{
@Override
public void run() {
index++;
str = "第" + index + "个str";
threadStr.set("第" + index + "个threadStr");
}
}
}
这个例子中,从str和threadStr变量的打印结果可以看出来。str被所有的线程读和写,threadStr在每个线程内部开辟了一块线程专享的区域。接下来,我们看一下具体实现。
先看一下构造函数
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
构造函数是空的,但是,该类有一个私有整型常量threadLocalHashCode。nextHashCode()方法我们就不看了,省的一如源码深似海。看鸟文的话,大概就是每new一个ThreadLocal变量的时候,就会生成一个散列码,该码非极端情况下与某个整数取模后不容易冲突(这句话有点迷吧,其实我也不懂)
然后看一下set方法
/**
* 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);
else
createMap(t, value);
}
容易看出,这个方法设置每个线程自己的value,相当于当前线程是key,然后得出一个ThreadLocalMap。显然,这个map用来保存线程内部的值,既然是map当然每个线程可以保存多个数值了,该map的value我们猜一下就是我要保存的具体的值,估计是用Object类声明的。那key是什么呢?我们看下ThreadLocalMap类的构造方法。
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* 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);
}
我的天!这个类没有继承我们想象中的HashMap,或者是ConcurrentMap,但是看过,Map的内部实现的同学应该可以发现,这个Map的实现和HashMap的实现简直就是小巫见大巫,有没有。它在构造函数中做了如下几步:
- 初始化一个大小为16的Entry数组
- 通过上面说过的很迷的HashCode散列值与15取模得到将要存储在数组中的索引值
- 构造Entry,然后保存进去
- 长度设置为1
- 设置要扩容的限制大小为16的2/3
我们看到这个不就是用数组实现的Map嘛,看过HashMap实现的我们,觉得洒洒水啦。
Map的set和get方法就不分析了。ThreadLocal的get方法我们还是要贴出来的,毕竟是我们主要分析的东西
/**
* 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();
}
可见,是获取到当前线程,用作key获取到Map,然后用当前this获取到Entry实体。最后当然获取到了存储的value。
我编码,我快乐~