从ThreadLocal的作用进行分析,你会更加了解它

本篇文章主要讲解Java并发中的一个重要知识点—ThreadLocal

ThreadLocal简介

ThreadLocal是一个本地线程副本变量类,它也是线程内部类的存储类,可以在指定线程内部存储数据,且只有指定的线程可以获取存储的数据。

ThreadLocal提供了线程内存储变量的能力,这些变量在每一个线程中都是相互独立的,通过set和get方法可以得到当前线程对应的值。

ThreadLoacl的相关方法

1.set()方法

public void set(T value) {
   //获取当前线程
   Thread t = Thread.currentThread();
   //存储的数据结构类型,获取当前线程创建的ThreadLocalMap
   ThreadLocalMap map = getMap(t);
   //如果存在map就直接set,没有则创建map并set
   if (map != null)
	map.set(this, value);
   else
	createMap(t, value);
}

从set()方法可以看到:
1).每次set时都先获取当前的线程,在当前线程的基础上进行操作。
2).获取该线程对象的ThreadLocalMap
3).如果ThreadLocalMap存在,就直接set,没有就先创建再set

看下ThreadLocalMap

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
}

看完ThreadLocalMap后发现:ThreadLocalMap是ThreadLocal的一个静态内部类,其中又创建了静态类Entry用来存储变量,且Entry中的key是ThreadLocal的对象,value在ThreadLocal确定的下标位置。

再看一下getMap()方法和createMap()方法

//ThreadLocal类中获取Thread类中的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {	
	return t.threadLocals;    
}
//创建map
void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
}

从getMap()可以直接获取当前线程的局部变量threadLocals(ThreadLocalMap类型)。

从createMap()方法可知:创建map时,实例化一个新的ThreadLocalMap,并赋值给当前线程的成员变量threadLocals。

看下threadLocals(ThreadLocalMap类型)的创建

ThreadLocal.ThreadLocalMap threadLocals = null;

threadLocals是ThreadLocal中的静态内部类ThreadLocalMap类型的一个对象,初始化为null。

再看下ThreadLocalMap的set方法就大功告成啦!

//ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    //获取 hash 值,用于数组中的下标
    int i = key.threadLocalHashCode & (len-1);

    //如果数组该位置有对象则进入
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        //k 相等则覆盖旧值
        if (k == key) {
            e.value = value;
            return;
        }

        //此时说明此处 Entry 的 k 中的对象实例已经被回收了,需要替换掉这个位置的 key 和 value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //创建 Entry 对象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
    
    //获取 Entry
    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); 
   }
}

其实,通过源码可知,ThreadLocalMap的一些方法和HashMap的方法很类似,都是先获取key的hash值,确定key在table中的索引位置,然后再判断set需要修改的位置和当前获取的位置是否一致,如果一致说明找到数组位置了则覆盖对应的值。

总结一下主要步骤如下:
1).创建数组
2).获取key的hash值,作为数组的下标
3).如果数组当前位置有对象,获取此位置
4).判断key和当前数组位置是不是同一个位置,如果是同一个位置,则说明找到此位置的值,进行覆盖。

看到这里大家可能很混乱,为了研究ThreadLocal的set()方法,竟然带出了一系列的方法,那么,如何将它们整合到一起呢?

我们还是回到最初的ThreadLocal的set()方法,一步步解读:

public void set(T value) {
   //获取当前线程
   Thread t = Thread.currentThread();
   //存储的数据结构类型
   ThreadLocalMap map = getMap(t);
   //如果存在map就直接set,没有则创建map并set
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}

解读步骤:
1.调用ThreadLocal的set()方法时,会先获取当前线程。
2.获取当前线程ThreadLocal的静态内部类,通过getMap()方法获取,getMap()方法返回的是当前线程threadLocals(ThreadLocalMap类型),然后在此map上进行值的存储方式为Entry<ThreadLocal实例对象,副本变量>。例如开启数据库连接时为set(getConnection);
存储为:
Entry<ThreadLocal实例,getConnection()>,此时,每个线程都进行各自的数据库连接。
3.如果存在ThreadLocalMap的对象,就直接set
4.如果没有创建ThreadLocalMap的对象,则使用当前线程创建,使用creatMap()方法,实例化一个新的ThreadLocalMap,并赋值给当前线程的threadLocals。

总结:
1.ThreadLocal在每个线程中都创建了一个ThreadLocalMap对象,且所有的操作都在这个对象里。
2.ThreadLocalMap是ThreadLocal的静态内部类,用Entry来进行存储。
3.调用ThreadLocal的set()方法时就是对ThreadLocalMap进行设置值,key是ThreadLocal对象。

ThreadLocal、ThreadLocalMap和Thread的关系

某个线程运行->线程为每个ThreadLocal创建了一个ThreadLocalMap对象->ThreadLocalMap中用Entry来进行存储,key为ThreadLocal的引用,value是对应的值。

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

看完set()方法,发现get()方法简单了许多,其中大体和set()方法类似,从Entry中获取变量是使用map.getEntry()方法。

总结:我们使用ThreadLocal类是为了使得每个线程在处理各自的变量时互不干扰,而ThreadLocal的互不干扰就体现在:每次调用其方法时都先获取当前的线程,返回当前线程的ThreadLocalMap,实际上ThreadLocal并不会存储值,真正存储值得是它的静态内部类ThreadLocalMap,而ThreadLocal调用set()和get()方法时,也会传递给ThreadLocalMap进行对应的操作,真正实现了一个线程操作线程自己的局部变量。

最重要的一点:ThreadLocal每次都会获取当前线程的ThreadLocalMap,然后在此map的基础上进行数据的存储,由于map都是当前线程创建的,数据自然就是当前线程所拥有的,与其它线程互相独立。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值