开始学习ThreadLocal相信大家对并发编程都有一点点了解了,在多个线程访问一个共享变量的时候,为了保证线程安全,一般对于共享变量的访问需要进行适当的同步,就需要对变量进行加锁,这显然增加了使用者的负担,那么是否有一种方案让每一个线程访问自己的变量呢?不用你去思索了,JAVA开发者已经帮你想好了,使用ThreadLocal可以帮你解决这样的困惑。
如上图可以看出,ThreadLocal为每一个线程创建了一个资源对象,线程之间的资源是完全独立的。
如上图:每一个线程都有自己的ThreadLocalMap,也就是Thread中的threadlocals属性,同时每一个ThreadLocalMap中又由一个Entry组成的数组构成。
在ThreadLocalMap中,存储的主要是Entry对象,而Entry又是由key value构成的,每一个key都是一个ThreadLocal的弱引用,当ThreadLocal没有强引用时就会被垃圾收集器回收。
这里提到了弱引用,就说一下JAVA 常见的四种引用,他们的区别:
下面有一段代码,看看大家是否可以分析清楚每一步都做了一些什么,如果可以完全明白每一步都是如何做的那么后面的部分就可以直接跳过了。
public class ThreadLocalTest {
private List<String> messages = Lists.newArrayList();
public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new);
public static void add(String message) {
holder.get().messages.add(message);
}
public static List<String> clear() {
List<String> messages = holder.get().messages;
holder.remove();
System.out.println("size: " + holder.get().messages.size());
return messages;
}
public static void main(String[] args) {
ThreadLocalTest.add("你猜猜我还在不在");
System.out.println(holder.get().messages);
ThreadLocalTest.clear();
}
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
阅读源码可以很清楚的看到其实withInitial方法,通过传入参数supplier创建了一个SuppliedThreadLocal类的对象。
public T get() {
//获得当前对象
Thread t = Thread.currentThread();
//获得Thread线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获得当前线程ThreadLocal所对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//没有ThreadLocalMap这里使用初始化新创建一个
return setInitialValue();
}
由上面的get方法可以看出,get方法首先通过当前线程来获得线程私有的ThreadLocalMap,如果没有则新创建。如果有则判断是否有ThreadLocal对应的Entry,如果有则返回Entry中的value值。即使是get方法也会初始化对象。
public void set(T value) {
//获得当前线程
Thread t = Thread.currentThread();
//通过当前线程获得ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如上述代码,ThreadLocal的set方法会首先获得当前线程的ThreadLocalMap,如果不存在则新建,如果存在就调用ThreadLocalMap的set方法将数据放入ThreadLocalMap中。
// ThreadLocal的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//移除ThreadLocalMap中以当前ThreadLocal为key的entry
m.remove(this);
}
// ThreadLocalMap的remove方法
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//遍历查找entry的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//查看通过hash定位到的entry是否正确
if (e.get() == key) {
//将entry引用置为null
e.clear();
expungeStaleEntry(i);
return;
}
}
}
对于上面的remove代码可以看出,在ThreadLocal进行remove操作的时候,实际上将ThreadLocal对应的entry从ThreaLocalMap中的table表中的entry引用置为null。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如上面的代码,t.threadLocals返回当前对象自己的threadLocals。
private Entry getEntry(ThreadLocal<?> key) {
//通过计算key的Hash值来确定key在ThreadLocal table中的下标
int i = key.threadLocalHashCode & (table.length - 1);
//获得Entry对象
Entry e = table[i];
//判断定位到的entry是否满足要求
if (e != null && e.get() == key)
return e;
else
//没有找到情况的处理
return getEntryAfterMiss(key, i, e);
}
上面代码可以看出,getEntry方法通过对key值的计算定位到entry在table中的位置,从而找到对象并返回。
/**
* 初始化线程的ThreadLocalMap
*/
private T setInitialValue() {
//调用子类重新的初始化方法,返回创建的对象
T value = initialValue();
Thread t = Thread.currentThread();
//获得当前线程的ThreadLocalmap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//创建新的ThreadLocalMap
createMap(t, value);
return value;
}
如上面的代码,完成了线程ThreadLocalMap初始化工作,如果Map没有创建需要重新创建。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建table用于存储ThreadLocalMap中的entry
table = new Entry[INITIAL_CAPACITY];
//firstkey的hash值
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//将ThreadLocal放在Entry中并放入到ThreadLocalmap的table中
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
如上图代码,可以看出ThreadLocalMap创建的过程。
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;
//获得table数组的长度
int len = tab.length;
//结算key在table中的下标
int i = key.threadLocalHashCode & (len-1);
//遍历获得key所对应的entry
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;
}
}
//使用key value创建entry对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
相信如果你可以看到这里已经对Threadlocal有了更深入的了解了,技术需要不断探索,需要不断学习与发现。希望大家有任何疑问可以在评论区留言。