下文笔者讲述多线程ThreadLocal功能简介说明,如下所示
ThreadLocal功能: 实现线程间的数据隔离 ThreadLocal在每个线程内部中维护一个变量,其他线程无法访问自己的变量
ThreadLocal简单使用
创建一个简单的User类
public static class User {
private int age = 0;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
定义ThreadLocalDemo
public class ThreadLocalDemo implements Runnable {
//创建线程局部变量UserLocal用来保存User对象
private final static ThreadLocal UserLocal = new ThreadLocal();
//对比实现,直接保存到对象里
public User stu = new User();
public void run() {
accessUser();
}
public void accessUser() {
//获取当前线程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
//产生一个随机数并打印
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
//获取一个User对象,并将随机数年龄插入到对象属性中
User User = getUser();//getThreadLocalUser
User.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + User.getAge());
System.out.println("thread " + currentThreadName + " second read age is:" + User.getAge());
}
protected User getThreadLocalUser() {
//获取本地线程变量并强制转换为User类型
User User = (User) UserLocal.get();
//线程首次执行此方法的时候,UserLocal.get()肯定为null
if (User == null) {
//创建一个User对象,并保存到本地线程变量UserLocal中
User = new User();
UserLocal.set(User);
}
return User;
}
protected User getUser() {
return stu;
}
protected synchronized User getUserSynchronized() {
return stu;
}
}
3. 来测试一下吧
同时运行两个 ThreadLocalDemo 线程。
public static void main(String[] agrs) {
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
调用getUser方法
b is running!
a is running!
thread b set age to:23
thread a set age to:88
thread b first read age is:23
thread a first read age is:88
thread a second read age is:88
thread b second read age is:88
可以看到直接使用 stu 变量的话,线程 b 第二次读取的时候其实是读取到了线程 a 设置之后的值。
看看放在 ThreadLocal 里面,换成调用 getThreadLocalUser 方法
a is running!
b is running!
thread a set age to:65
thread b set age to:72
thread b first read age is:72
thread a first read age is:65
thread b second read age is:72
thread a second read age is:65
a,b 两个线程两次都只是各自读取自己的局部变量
ThreadLocal实现原理
ThreadLocal 的 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);
}
ThreadLocalMap类
是ThreadLocal的一个内部类
ThreadLocalMap 内部有一个Entry类
这是一个(key,value)形式的弱引用
为什么要把key作为弱引用呢
是为了让ThreadLocalMap尽可能的小
在 key 不被引用的时候就回收
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
再回到上面的 set 方法
首先是通过getMap(t)方法拿到当前线程ThreadLocalMap对象
Thread 类里面就有一个名为threadLocals的ThreadLocalMap 对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如果为空,则去创建
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
把新的 ThreadLocalMap 对象赋值给线程t的 threadLocals 对象
看看 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);
}
table是一个Entry 数组
按照上面的 Entry(ThreadLocal<?> k, Object v) 构造函数
初始化 ThreadLocalMap 对象的时候就会把 ThreadLocal 对象作为key
把value放到一个 Entry 对象中
并且保存到 table 数组中
如果这个对象不为空
则直接调用 ThreadLocalMap 的 set方法
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);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
遍历 table 数组
如果当前的 ThreadLocal 对象和其中已经保存的某个 Entry 的 key 相等
则把这个 Entry 的value设置为传进来的值。如果发现key为空(是的,会出现k是null的情况)
所以会接着在replaceStaleEntry重新循环寻找相同的key,找到之后再赋值
所以说我们通过 ThreadLocal 传进来的值
其实都保存在了 ThreadLocalMap 的 Entry对象里
ThreadLocal 的 get 方法
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();
}
就是在Entry中通过当前 ThreadLocal 对象索引到它对应的值
getMap 方法上面已经看过了。看看 ThreadLocalMap 的 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);
}
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


