案例
定义一个静态变量,定义5个Thread线程去访问这个静态变量
public class ThreadLocalTest {
private static int num = 0;
public static void main(String[] args) {
Thread [] threads=new Thread[5];
for(int i=0;i<5;i++){
threads[i]=new Thread(()->{
num =num+5;
System.out.println(Thread.currentThread().getName()+"-------"+num);
});
}
for(int j=0;j<5;j++){
threads[j].start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果
如果想让每个线程起始值都为初始值,怎么实现?
通过线程副本ThreadLocal实现
public class ThreadLocalTest {
private static int num = 0;
//线程副本
private final static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
//初始值为0
return 0;
}
} ;
public static void main(String[] args) {
Thread [] threads=new Thread[5];
for(int i=0;i<5;i++){
threads[i]=new Thread(()->{
//num =num+5;
num=threadLocal.get();
num=num+5;
//更新threadLocal在thradlocalmap里存储的值(本质为该线程在entry对应下标的值)
threadLocal.set(num);
System.out.println(Thread.currentThread().getName()+"-------"+num);
});
}
for(int j=0;j<5;j++){
threads[j].start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这样每个副本变量存储的值都是单独存在,互不影响
ThreadLocal底层是怎么实现的呢?
源码讲解
进入threadLocal.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;
}
}
//map为空时,进行初始化
return setInitialValue();
}
源码分析:
Thread t = Thread.currentThread();
获取当前线程,
进入getMap(t)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
此时发现每个线程都有一个成员变量ThreadLocalMap threadLocals,也就是说每个线程副本存储数据都有各自的ThreadLocalMap进行存储并独立的互不干扰,接下来继续分析源码
进入setInitialValue()源码
//map为空时,进行初始化
return setInitialValue();
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;
}
进入createMap(t, value);
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
进入ThreadLocalMap构造方法
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);
}
此时我们或许会更清楚,Thread Map里面用Entry数组来存储数据,数组里存放的是firstKey,firstValue形式的值,firstKey是创建的ThreadLocal对象 firstValue是里面存储的值,其实就是(key,value)形式,有这么一行代码,firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)是获取数组的下标,对于同一个ThreadLocal的对象,他们获取的数组下标是一样的,也就是说同一线程副本在ThreadLocalMap存储的位置是相同的,一个线程可以有多个线程副本,同一线程中只是每个线程副本在ThreadLocalMap存储的位置是不相同的
在补充一个方法
threadLocal.set(num);
该方法作用,更新threadLocal在thradlocalmap里存储的值,本质为更改该线程副本在对应的entry数组下标的值,源码如下:
private void set(ThreadLocal<?> key, Object value) {
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);
//数值加1
int sz = ++size;
//达到一定长度扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
文字和图分析
这里源码可能不好理解,这里用文字分析:
每个线程都有各自的ThreadLocalMap成员变量,作用就是存储数据的,ThreadLocalMap内用用Entry数组来存储数据,也许大家会很困惑,上图明明是键值对形式的,数组怎么存储的啊?又怎么确保每个线程副本之间的值互补干扰的?
firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)是获取数组的下标,也就说每个线程的threadLocalHashCode是不同的,获取的数组下标不同,并将真正的值作为数组的值,这样就保证了多个线程副本的独立性。也就是说每个线程的Thread Local Map各自独立,每个线程副本之间也互相独立。