ThreadLocal源码角度详细解析

ThreadLocal是每个线程绑定的本地对象,线程独立存储。在开发中常用于事务管理。源码分析显示,ThreadLocal使用弱引用来防止内存泄漏,当ThreadLocal对象被回收,需手动调用remove避免内存问题。
摘要由CSDN通过智能技术生成

1、从简单层面理解

ThreadLocal是绑定在每一个线程上的本地对象,线程独立拥有,线程存在,ThreadLocal就一直存在。比如我们new了一个ThreadLocal对象,此时有两个线程A和B,在A线程里set一个一个对象到ThreadLocal里面,过2sB线程去读取是读取不到的读到的是null,因为ThreadLocal是线程独立拥有的,只能自己拿到别的线程拿不到。

测试代码如下:

public static void main(String[] args) {
        ThreadLocal<Person> t = new ThreadLocal<Person>();
        // A线程
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t.set(new Person(1L, "测试"));
            System.out.println(t.get());
        }).start();
        // B线程
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t.get());
        }).start();
    }

    @AllArgsConstructor
    @Data
    static class Person{
        private Long id;
        private String name;
    }

2、开发中的典型应用

我们开发常用的事务注解@transactional就是利用ThreadLocal的原理,保证当前线程里多个方法拿到的数据库连接都是同一个连接

3、从源码角度看底层实现

public static void main(String[] args) {
        ThreadLocal<Person> t = new ThreadLocal<Person>();
        t.set(new Person(1L, "测试"));
        t.remove();
    }

 @AllArgsConstructor
 @Data
 static class Person{
      private Long id;
      private String name;
  }  

我们首先new了一个ThreadLocal对象,然后将Person对象set进去,我们点击set方法进去看:

可以看到这个set方法里面的实现是先拿到当前线程,然后调用getMap(t)方法

 而这个getMap方法的实现又是调用的当前线程的成员变量threadLocals方法直接得到的。

至此,我们知道了map的由来,再去看map.set(this, value)这个方法是将ThreadLocal对象作为key,set的具体对象值作为value存进map中,然后我们在点击map.set()方法进去看下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;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

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

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 可以看到map.set里面具体的做法是将key和value放到一个Entry对象中,这个entry其实就可以看做是map的一条记录行,我们在点击Entry进去看:

 可以看到Entry对象是继承WeakReference弱引用对象的,并且它的构造器中是调用父类中的key的,即放到WeakReference对象中的ThreadLocal对象。

好了,所有源码看完,我们可以得出结论:

我们new出的任何线程Thread对象,对象里有一个成员变量,这个成员变量指向一个map,最开始是null值,当我们new一个ThreadLocal对象出来,然后调用它的set方法,那么这个方法就会把ThreadLocal<T>对象作为key,set的对象做为value存进map中。map的set方法里面具体是把key和value放到一个new的Entry里面,这个entry其实就可以看作map的一条记录行,并且这个entry继承了WeakReference弱引用,以及它的构造器是调用了父类的方法,这个方法是我们new WeakReference时放的对象,即上面说的key。所以map里面的key时通过一个弱引用指向的Thread Local对象。

 4、分析ThreadLocal防止内存泄漏的问题

从上面的源码我们可以看到:

问题1:我们一开始new了一个ThreadLocal对象是强引用指向的,但是在往map里面set的时候,又有一个弱引用指向ThreadLocal对象,这是为什么?为什么要使用弱引用?

是为了防止内存泄露,如果map里面的set也用强引用指向ThreadLocal,那么这样GC的时候即使我们外面一开始强引用的对象不再被引用了,但是map里面的还在引用无法回收,而弱引用遇到GC会直接被回收。

问题2:当ThreadLocal对象被回收后,map里面的key就为null了,那么map里面的这条记录就没有意义了,但是垃圾回收是不会回收这个的,需要我们手动删除,所以在我们不用的时候需要加上remove(看上面的代码)不然也容易产生内存泄漏。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值