ThreadLocal源码解析、弱引用的理解

ThreadLocal通过在每个线程内部维护一个独立的变量副本,实现线程间的变量隔离。文章通过示例代码和源码分析,解释了ThreadLocal如何保证每个线程的ThreadLocal变量互不影响,并介绍了ThreadLocalMap的弱引用特性,防止内存泄漏。
摘要由CSDN通过智能技术生成

0、引言

        我们知道在通常情况下,对于主存中的变量,每一个线程都能够访问并修改该变量(或对象)。与之相对的,如果我们需要实现每个线程拥有自己专属的本地变量,该如何操作呢?
        此时引出ThreadLocal类,通过ThreadLocal可以实现全局变量在多线程环境下的线程隔离每个线程都可以独立地访问和修改自己的全局变量副本,不会影响其他线程的副本。这在某些场景下可以简化代码的编写和理解。

1. 源码解析

1.1 示例代码

        我们先从一段简单的代码示例入手:

package Thread_;

public class ThreadLocal {
    private static java.lang.ThreadLocal<Integer> counter = new java.lang.ThreadLocal<>();

    public static void main(String[] args) {
        Runnable runnable = () -> {
            // 获取当前线程的计数器值,初始值为0
            int count = counter.get() == null ? 0 : counter.get();
            System.out.println(Thread.currentThread().getName() + " 的计数器值为: " + count);

            // 对计数器进行累加操作
            counter.set(count + 1);

            // 模拟耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 再次获取计数器值
            count = counter.get();
            System.out.println(Thread.currentThread().getName() + " 的累加后计数器值为: " + count);
        };

        // 创建三个线程并启动
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//结果如下:
Thread-1 的计数器值为: 0
Thread-0 的计数器值为: 0
Thread-2 的计数器值为: 0
Thread-1 的累加后计数器值为: 1
Thread-2 的累加后计数器值为: 1
Thread-0 的累加后计数器值为: 1

        在代码中,我们定义了一个ThreadLocal类的整形对象counter,用三个线程进行累加操作。结果我们发现,counter的值并没有变为3,而是每个线程有一个自己的counter值,分别为1。由此引出ThreadLoca的作用:

        ThreadLocal对象在每个线程内是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己独有的ThreadLocal对象的值),即,实现了线程范围内的局部变量的作用。

 1.2 线程独享原因:源码解析

        首先我们可以推测,如果要保证每个线程独享一份数据,那这份数据应该要能够从线程内部进行引用。
        实际上,线程的栈中存放了ThreadLocal.ThreadLocalMap这么一个属性(初始化为空),ThreadLocalMap是ThreadLocal的一个静态内部类,以后数据就要以ThreadLocalMap的形式在堆中实例化,并让threadLocals成为它的引用!这样就完成了线程的独享了。

public class Thread implements Runnable{
    ...
    ...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

        我们回到示例中,注意:ThreadLocal<Integer> counter这个ThreadLocal对象的set方法,跟进源码,详细解释如下:

// 对计数器进行累加操作
counter.set(count + 1);

//源码1. set源码:
public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//让map成为该线程内的threadLocals所引用的堆中的对象!
//见源码2. 
//其实就是去尝试引用实例化的ThreadLocalMap,但此时初始化为null,所以我们看下面的判断:

        if (map != null) {
            map.set(this, value);//不为空,已经有map,就把堆中的值赋值为counter,count + 1
        } else {
            createMap(t, value);//为空,创建ThreadLocalMap的对象
            //但要注意,createMap并不是让map实例化,见下面源码3.
        }
    }

//源码2. getMap源码:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    //意思就是,返回这个t线程的ThreadLocal.ThreadLocalMap threadLocals = null里面这个threadLocals 对象
    }

//源码3. createMap源码:
//传入的this,其实就是counter。firstValue就是相应的值count + 1。
//注意,是将t.threadLocals线程内的这个ThreadLocalMap对象实例化,所以线程内部的这个对象指向了堆中内存的new...,实现了线程内部的数据独立。
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

概括:

        看似是向ThreadLocal存入一个值,实际上是向当前线程对象中ThreadLocalMap对象存入值(如果为空,则实例化当前线程对象中的ThreadLocalMap对象)ThreadLocalMap我们可以简单的理解成一个Map,Map存的key就是ThreadLocal实例本身(counter),value是具体的值。

        get方法同理,也是先取出当前线程对象,再取出其指向的map里的值:

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

1.3 ThreadLocalMap的key的弱引用

源码如下:

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

        通常ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

        ThreadLocal中一个设计亮点是ThreadLocalMap中的Entry结构的Key用到了弱引用。
        试想如果使用强引用,如果ThreadLocalMap的Key使用强引用,那么Key对应的ThreadLocal对象在没有被外部引用时仍然无法被GC回收,因为Key存在于ThreadLocalMap中,而且线程是长时间存活的。这就可能导致ThreadLocal对象无法被回收,从而造成内存泄漏。

        使用了弱引用的话,JVM触发GC回收弱引用后,ThreadLocalMap中会出现一些Key为null,但是Value不为null的Entry项,这些Entry项如果不主动清理,就会一直驻留在ThreadLocalMap中。此时,ThreadLocal在下一次调用get()、set()、remove()方法就可以删除那些ThreadLocalMap中Key为null的值,起到了惰性删除释放内存的作用。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好奇的7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值