1概述
线程本地变量(ThreadLocal)位每一个线程创建一个变量副本,从而线程就可以单独使用自己拥有的变量副本,而相互之间不产生影响。而线程本地变量与线程同步机制中的共享变量有什么区别呢?很明显,每个线程对共享变量的修改对于其余线程是可见的,而每个线程对线程本地变量的修改对于其余线程是不可见的。
2示例
package com.liutao.concurrent;
public class ThreadLocalDemo {
public static ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
int num;
for (int i = 0; i < 3;i++){
num = ThreadLocalDemo.integerThreadLocal.get();
System.out.println(Thread.currentThread().getName()+":"+num);
ThreadLocalDemo.integerThreadLocal.set(num+1);
}
}
}
执行结果:
Thread-0:0
Thread-1:0
Thread-2:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-2:1
Thread-2:2
Thread-0:2
我们可以看见上面三个线程执行的内容是相同的,相互之间没有任何影响。
针对ThreadLocal常用的四个方法如下:
- get:返回当前线程中变量副本的值。
- set:设置当前线程中变量副本的值。
- remove:移除当前线程中变量副本的值。
- initialValue:返回变量副本的初始值。当线程第一次调用变量副本的时候将会调用这个方法,除非在调用变量副本的get方法之前已经调用了set方法进行设置。当然上面的示例我们可以看见,我们重写了initialValue方法。
3源码分析
3.1ThreadLocalMap源码分析
通过查看ThreadLocal的set、get方法我们可以发现ThreadLocal的实现主要是依靠了ThreadLocalMap这个内部类。那么首先我们来看ThreadLocalMap这个内部类的实现。
通过查看源码我们可以发现ThreadLocalMap是使用Entry来进行key和value的存储的。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 和线程产生关联的值 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这个Entry继承自WeakReference,并使用ThreadLocal作为key。可以看见针对弱引用,当key为null的时候将被GC回收。
那么针对ThreadLocalMap我们仅仅分析两个核心方法即可。
/**
* 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;
//根据ThreadLocal的hashcode查找对应元素在数组中的位置
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;
}
//key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//ThreadLocal对应的key不存在,也没有Entry值,则创建一个新的
tab[i] = new Entry(key, value);
int sz = ++size;
//cleanSomeSlots清楚key == null 的Entry,如果没有清除成功,并且数组中的元素大于了阀值则refresh
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
针对上面的源码,我们可以看见ThreadLocalMap初始的时候有一个table,并且初始容量为16。
/**
* 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;
nextIndex方法的源码如下:
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
可以发现轮询table获取的Entry的最大索引为len-1,当大于len-1的时候索引为0。
针对cleanSomeSlots和refresh,我们可以看见,最终都是调用expungeStableEntry来删除Entry的value并置空节点。
/**
* 清除成旧的Entry(key==null)
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
/**
* 清除整个table的成旧数据
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
/**
* 从新hash计算从staleSlot节点到下一个空节点中的节点
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//删除指定节点位置的值并置空节点
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
//从新计算hash,直到遇到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;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
通过上面我们分析了ThreadLocalMap的set方法,接下来我们分析getEntry,源码如下:
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);
}
通过key计算得到索引值过后直接从table中获取值,如果获取到就返回,没有获取到就调用getEntryAfterMiss,那么getEntryAfterMiss又做了什么呢?
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
从i位置开始查找entry,当找到 entry不为空,并且找到对应的key的时候,就返回entry,如果key ==null就直接清除掉位置i。
前面分析了ThreadLocalMap中的核心方法,那么针对ThreadLocal中的方法相对来说就简单多了。
3.2ThreadLocal源码分析
(1)get方法
public T get() {
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
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();
}
返回当前线程中变量副本的值,如果没有 ,就初始化。
(2)set方法
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,当然如果ThreadLocalMap不存在,则先创建ThreadLocalMap,然后再进行初始值设置。
针对remove和initialValue就不继续讲解了,都比较简单。
4应用场景
最常见的ThreadLocal的使用时用来解决数据库连接和session管理。
具体示例,后期在源码中发现添加。