欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ThreadLocal源码理解

程序员文章站 2022-03-16 15:01:27
ThreadLocal是如何实现线程间数据隔离的下面是 ThreadLocal类里 set 方法的源码,我们可以从这个方法去理解实现线程间数据隔离的原理。// ThreadLocal 类 public void set(T value) { // 1、获取当前线程 Thread t = Thread.currentThread(); // 2、获取当前线程的ThreadLocalMap对象 ThreadLocalMap map =...

1、ThreadLocal是如何实现线程间数据隔离的

下面是 ThreadLocal类里 set 方法的源码,我们可以从这个方法去理解实现线程间数据隔离的原理。

	// ThreadLocal 类
    public void set(T value) {
        // 1、获取当前线程
        Thread t = Thread.currentThread();
        // 2、获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 3、往当前对象的ThreadLocalMap对象里设置值
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocalMap map = getMap(t)
这句话是 ThreadLocal 实现线程隔离的关键。

下面我们来看看 getMap() 方法实现

    ThreadLocalMap getMap(Thread t) {
    	// 这里返回的是当前线程的threadLocals属性
        return t.threadLocals;
    }

因为每个线程来 set 属性的时候,都是返回各个线程自己的 threadLocals 的,然后将数据保存到当前线程的threadLocals变量中,所以,自然就实现了线程隔离。

2、为什么 ThreadLocalMap 的 key 是弱引用

前面我们说了,ThreadLocal 在调用 set 方法的时候,是将值设置到了各自线程的 threadLocals 里。那么,threadLocals 里是如何存储的呢?首先,我们很容易想到,一个线程里,肯定不可能只存储一个ThreadLocal类型的对象,所以,线程的 threadLocals 里肯定是有一个数组,这个数组的里的每一项,便是一个 ThreadLocal 的实例对象。

// 这是Thread类中,threadLocals的声明语句
ThreadLocal.ThreadLocalMap threadLocals = null;

接下来,我们就来验证一下我们上面的猜想,去看一下 ThreadLocal.ThreadLocalMap的代码:

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        // 保存当前线程的所有ThreadLocal实例的数组
        private Entry[] table;

        // ... 省略其它代码 ...
}
  • 为什么 ThreadLocalMap 的 key 是弱引用

理解了线程是如何存储ThreadLocal的实例之后,我们再回到主题上来,为什么 ThreadLocalMap 的 key 是弱引用?

java 中,引用分为 强、软、弱、虚 四种引用。弱引用的意思是,如果一个对象只有弱引用指向它,只要遭遇gc就会被回收。

我们试想一下,如果 ThreadLocalMap 的 key 是强引用,当引用 ThreadLocal 的那个对象都被回收,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,那么这个 ThreadLocal 便不会被回收,这样就会导致内存泄漏

3、如何避免ThreadLocal 的内存泄漏

上面我们说了,在 Entry 中,key设置为弱引用,是为了防止 key 的内存泄漏。但是,我们想想,如果引用 ThreadLocal 的那个对象被回收了,也就是说 Entry 中的key 也会被回收,但是 Entry 中的value又是一个强引用啊。总的来说就是,当引用 ThreadLocal 的对象被回收时,Entry中的key会被回收,而value不会,这样就出现了内存泄漏,那么,我们该如何避免这种内存泄漏呢,其实很简单,我们只要在使用ThreadLocal 的时候,如果不用了,一定记得调用 ThreadLocal 的 remove 方法,将这个 value 移除掉就可以了。

4、ThreadLocal 是如何解决 Hash 冲突的

我们刚才提到,threadLocals 里,是用一个 Entry的数组来存储 ThreadLocal 的实例的,在存储的时候,根据 Hash 判断 ThreadLocal 实例在数组中的位置,既然是根据 Hash 来确定位置的,就难免会发生 Hash 冲突,那么 ThreadLocal 是如何解决 Hash 冲突的呢?我们来看一看 ThreadLocal 的私有set方法就知道了。

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    // 采用线性探测的方式来解决hash冲突
    // 如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // 找到key对应的 ThreadLocal ,便修改对应的值
        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();
}

本文地址:https://blog.csdn.net/liukunc9/article/details/109922570

相关标签: 多线程 java