ThreadLocal初次见面

ThreadLocal是什么?🚚

开始学习ThreadLocal相信大家对并发编程都有一点点了解了,在多个线程访问一个共享变量的时候,为了保证线程安全,一般对于共享变量的访问需要进行适当的同步,就需要对变量进行加锁,这显然增加了使用者的负担,那么是否有一种方案让每一个线程访问自己的变量呢?不用你去思索了,JAVA开发者已经帮你想好了,使用ThreadLocal可以帮你解决这样的困惑。

传统方案的设计

在这里插入图片描述

ThreadLocal设计

在这里插入图片描述
如上图可以看出,ThreadLocal为每一个线程创建了一个资源对象,线程之间的资源是完全独立的。

ThreadLocal的存储结构 ❤️

在这里插入图片描述
如上图:每一个线程都有自己的ThreadLocalMap,也就是Thread中的threadlocals属性,同时每一个ThreadLocalMap中又由一个Entry组成的数组构成。
在ThreadLocalMap中,存储的主要是Entry对象,而Entry又是由key value构成的,每一个key都是一个ThreadLocal的弱引用,当ThreadLocal没有强引用时就会被垃圾收集器回收。

常见误区:

  • ThreadLocalMap是HashMap结构的,这里是完全错误的,其实ThreadLocalMap里面存储的是entry的数组,而entry中是key、value形式存储的。

拓展知识

这里提到了弱引用,就说一下JAVA 常见的四种引用,他们的区别:

  • 强引用:我们使用new关键字创建的对象就是强引用类型,只要有强引用存在,垃圾回收器永远不会回收对象,即使内存不足。
  • 软引用:使用SoftReference修饰的对象被称为软引用,软引用只有在内存溢出时会被回收。
  • 弱引用:使用WeakReference修饰的对象被称为是弱引用,只要发生垃圾回收,弱这个对象指被弱引用关联,就会被回收。
  • 虚引用:在 Java 中使用 PhantomReference 进行定义,虚引用中唯一的作用就是用队列接收对象即将死亡的通知。

源码分析 🪐

下面有一段代码,看看大家是否可以分析清楚每一步都做了一些什么,如果可以完全明白每一步都是如何做的那么后面的部分就可以直接跳过了。

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();
    }
}

输出结果:
在这里插入图片描述

开始分析

withInitial方法:
 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
 }

阅读源码可以很清楚的看到其实withInitial方法,通过传入参数supplier创建了一个SuppliedThreadLocal类的对象。

ThreadLocal的get方法:

在这里插入图片描述

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方法也会初始化对象。

ThreadLocal的set方法:

在这里插入图片描述

 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中。

remove方法:
// 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。

ThreadLocal的getMap方法:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

如上面的代码,t.threadLocals返回当前对象自己的threadLocals。

getEntry方法:
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中的位置,从而找到对象并返回。

setInitialValue方法:
	/**
	* 初始化线程的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没有创建需要重新创建。

createMap方法:
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创建的过程。

ThreadLocalMap的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;
    //获得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有了更深入的了解了,技术需要不断探索,需要不断学习与发现。希望大家有任何疑问可以在评论区留言。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值