线程本地变量:线程本地变量通常是一个类中的私有静态的成员变量。我们可以在不同的线程中的调用线程本地变量的get
和set
方法,来获取和设置当前线程中线程本地变量的值。在当前线程改变线程本地变量的值,并不会影响其他线程本地变量的值。
上面一段话有点抽象,先举个例子说一下。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//注释1处,在主线程中设置ThreadLocal保存的变量值
threadLocal.set("初始名称");
//注释2处
System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get());
new TestThread("线程甲", threadLocal).start();
new TestThread("线程乙", threadLocal).start();
}
}
class TestThread extends Thread {
private ThreadLocal<String> threadLocal;
public TestThread(String name, ThreadLocal<String> threadLocal) {
super(name);
this.threadLocal = threadLocal;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 6) {
//当i==6的时候替换成当前线程名
threadLocal.set(getName());
}
//获取
System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get() + ",i= " + i);
}
}
}
输出如下
main ,初始名称
线程甲 ,null,i= 0
线程甲 ,null,i= 1
线程甲 ,null,i= 2
线程甲 ,null,i= 3
线程甲 ,null,i= 4
线程甲 ,null,i= 5
线程甲 ,线程甲,i= 6
线程甲 ,线程甲,i= 7
线程甲 ,线程甲,i= 8
线程甲 ,线程甲,i= 9
线程乙 ,null,i= 0
线程乙 ,null,i= 1
线程乙 ,null,i= 2
线程乙 ,null,i= 3
线程乙 ,null,i= 4
线程乙 ,null,i= 5
线程乙 ,线程乙,i= 6
线程乙 ,线程乙,i= 7
线程乙 ,线程乙,i= 8
线程乙 ,线程乙,i= 9
在这个例子中,我们创建了一个ThreadLocal变量,内部保存的是一个String类型的变量。
private static ThreadLocal<String> name = new ThreadLocal<>();
在main方法的注释1处,我们在主线程中设置ThreadLocal变量值为初始名称
,所以在注释2处输出如下
main ,初始名称
然后我们新建了两个线程,在线程的run方法中首先从0到5threadLocal.get()
方法获取的值都是null。
然后从6开始把threadLocal设置为当前线程的名字,然后再调用threadLocal.get()
方法输出的就是线程对应的名字了。
源码分析
首先我们看一下ThreadLocal的构造方法
public ThreadLocal() {
}
看下ThreadLocal的set(T value)
方法相关操作
public void set(T value) {
Thread t = Thread.currentThread();
//注释1处
ThreadLocalMap map = getMap(t);
//注释2处,注意传入的this是作为key的。
if (map != null)
map.set(this, value);
//注释3处,
else
createMap(t, value);
}
在注释1处,首先获取当前线程的ThreadLocalMap对象。ThreadLocal的getMap方法。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread类的threadLocals变量是一个ThreadLocal.ThreadLocalMap
对象,用来保存与当前线程相关的ThreadLocal变量。
public class Thread implements Runnable {
//...
//用来保存与当前线程相关的ThreadLocal变量
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal.ThreadLocalMap
是ThreadLocal的一个静态内部类,只适合用来保存线程本地变量。ThreadLocalMap的实体使用弱引用来保存key。注意key是ThreadLocal<?> 。
static class ThreadLocalMap {
//初始容量,必须是2的幂
private static final int INITIAL_CAPACITY = 16;
//存储entry的表,根据需要调整大小。表的长度必须是2的幂
private Entry[] table;
//...
static class Entry extends WeakReference<ThreadLocal<?>> {
//和ThreadLocal关联的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
//使用弱引用来保存key
super(k);
value = v;
}
}
}
在set(T value)
方法的注释3处,如果获取到的map对象为null,则调用createMap方法。
创建一个ThreadLocalMap对象赋值给当前线程的threadLocals变量。同时保存了初始的key和value。注意key是ThreadLocal类型的
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap的两个参数的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//注释1处,看一看 threadLocalHashCode 是怎么计算的。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//保存初始的key和value
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
注释1处,threadLocalHashCode 是一个final类型的变量。而且是一个原子类型的变量。
private static AtomicInteger nextHashCode =
new AtomicInteger();
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
在set(T value)
方法的注释2处,如果获取到的map对象不为null,则调用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法。
ThreadLocalMap的set(ThreadLocal<?> key, Object value)
方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//注释1处,获取在map中的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//更新key对应的value
if (k == key) {
e.value = value;
return;
}
//替换指定位置上的键值对
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//加入到table中
tab[i] = new Entry(key, value);
int sz = ++size;
//是否需要缩减table或者扩容,如果是扩容的话,容量会增加到两倍
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set(ThreadLocal<?> key, Object value)方法,注释1处,获取在map中的位置,要注意一下这个key,threadLocalHashCode
是一个final 类型的变量,在第一次创建ThreadLocal对象的时候初始化,后面就不会变了。
接下来看一下ThreadLocal的get方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象map
ThreadLocalMap map = getMap(t);
if (map != null) {//如果map存在,返回对应的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//map不存在。调用setInitialValue方法
return setInitialValue();
}
方法内部判断如果线程的threadLocals
不为null,就从中取出对应的值并返回。
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);
}
如果map中没有对应的值,返回setInitialValue
方法的执行结果。
ThreadLocal的setInitialValue方法
private T setInitialValue() {
//注释1处,首先调用initialValue方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
ThreadLocal的initialValue方法默认返回null
protected T initialValue() {
return null;
}
我们可以重写ThreadLocal的initialValue方法来提供一个默认值,就像这样。
private static ThreadLocal<String> name = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello world";
}
};
总结:
- 实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的。
- 如果想在调用get方法之前不需要调用set方法就想得到一个默认的值的话,可以重写initialValue()方法。
Android 中的Looper 是使用ThreadLocal来保存的。
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));
}
参考链接: