1.使用demo
package com.xiayu.demo;
import lombok.extern.slf4j.Slf4j;
import java.util.Locale;
@Slf4j
public class ThreadLocalDemo {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 5;
}
}; //cout的初始值是0
private static ThreadLocal<Locale> language = new ThreadLocal<Locale>(){
@Override
protected Locale initialValue() {
return Locale.CHINA;
}
};//languag的初始值为
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
//language
Locale lan = language.get();
log.info(Thread.currentThread().getName() + ".language:" + lan);
language.set(Locale.ENGLISH); //每个线程可针对具体场景设置该线程需要的值
log.info(Thread.currentThread().getName() + ".language:" + language.get());
//--------------------------------------------------------------------------------
int num = count.get(); //每个线程获取count的一个拷贝
num += 5;
count.set(num); //进行加5,后再保存到线程里面
//... 进行了别的业务操作
count.get();//业务中需要获取到count的值
log.info(Thread.currentThread().getName() + ":" + count.get()); //再获取到count的值
count.remove();
language.remove();
}, "thread-" + i);
threads[i].start();
}
}
}
运行结果
2.源码
分析数据结构:一个线程可能有多个ThreadLocal变量,存储线程的ThreadLocal变量使用的是嵌套map,
a. set()方法
------------------------------------------------
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //如果线程ThreadLocalMap不为null,就进行set
else
createMap(t, value); //如果为null,就进行new 操作,t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap new操作
-------------------------------
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);
}
-----------------------------------------------
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; // table为存储线程局部变量 private Entry[] table;
int len = tab.length; //数组的长度
int i = key.threadLocalHashCode & (len-1); //取余(len-1)得到索引index
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { //如果e为null,nextIndex方法会找到下一个索引,即使到了最后一个,下一个索引是0 return ((i + 1 < len) ? i + 1 : 0); 目的是找到一个初始化的Entry
//如果线程局部变量已经被赋值了,e = tab[i]不会为null,就会走k == key的逻辑,如果线程局部变量没有被赋值,接下来就是找到一个已经new 过的entry,然后走 k == null的逻辑
ThreadLocal<?> k = e.get();
if (k == key) { //已经被赋值过后
e.value = value;
return;
}
if (k == null) { //找到的新创建的entry
replaceStaleEntry(key, value, i);//new 过了,但k为null,设置该tab[i] = value;
return;
}
}
//执行到这里说明,0-len全null,然后就new 一个Entry对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
b. get()方法
public T get() {
Thread t = Thread.currentThread(); //首先获取当前线程
ThreadLocalMap map = getMap(t); //ThreadLocalMap是存储每个线程的局部变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //this为当前线程局部变量
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; //获取局部变量
return result;
}
}
return setInitialValue();//如果ThreadLocalMap值为null的话就返回默认值
}
-------------------------
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap 为ThreadLocal的静态内部类,其实现是使用数组实现的,private Entry[] table; // 线程局部变量使用其hash值取余数组的长度作为key,使用数组索引表示该局部变量
c. remove()方法
首先获取到当前线程,然后获取当前线程的ThreadLocalMap,然后删除
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;
}
}
}
3.总结
线程局部变量真正的存储在Thread类的ThreadLocalMap中,其是基于数组实现的,局部变量类的hash code取余数组长度作为key,value就是局部变量类的值,操作Thread局部变量是在ThreadLocal类中完成的。通过Thread t = Thread.currentThread();获取当前线程。
4.具体使用场景
可以在一次请求中将请求的国家语言,数据库要连接的名称,表名都可以设置在线程局部变量中,然后在具体业务逻辑要使用到这些值时直接通过ThreadLocal类获取到这些值。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。