ThreadLocal学习笔记

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。

ThreadLocal实例是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值是被线程实例持有,它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。

ThreadLocal具体实现:每个线程对象内部有个ThreadLocalMap的成员变量threadLocals,它是一个map的结构,底层是Entry数组table

set操作:就以WeakReference包装ThreadLocal变量,即令其中的referent引用指向这个变量,并将这一个WeakReference作为key,要set的参数作为value,封装成一个Entry插入到map当中,如果出现hash冲突则通过线性再散列方法处理

get操作:首先从对应线程中拿到对应的threadLocals,也就是那个map,接着调用map.get方法,通过计算到的hash值和table.length-1相与,确定所在的位置并返回值。threadLocals允许有键为null的Entry存在

应用场景

1、Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。主要通过TransactionSynchronizationManager这个类。

2、在Android中,Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个Looper对象。

3、当一个线程经常遇到横跨若干方法调用,需要传递=上下文(Context),比如用户身份、任务信息,存在过渡传参的问题。可使用ThreadLocal,在调用前在ThreadLocal中设置参数,其他地方通过ThreadLocal的get方法去获取。

4、ThreadLocal结合AOP,实现日志打印,每个线程负责的请求完成之后再一起打印,多个线程之间互不影响。

5、多个线程同时使用SimpleDateFormat的时候,可能线程不安全,因为要操作同一个pattern成员变量,这时就可以将SimpleDateFormat用ThreadLocal封装。

源码

set源码:

public void set(T value) {
    Thread t = Thread.currentThread();// 获取当前线程
    ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
    if (map != null) // 校验对象是否为空
        map.set(this, value); // 不为空set
    else
        createMap(t, value); // 为空创建一个map对象
}

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

通过ThreadLocalMap去判断当前线程是否存在有map对象,而map对象则是通过当前Thread的threadLocals的变量中获取的。

每个线程Thread都维护了自己的threadLocals变量,当每个线程创建ThreadLocal,获取的数据是存在自己线程Thread的threadLocals变量里面的,从而实现了隔离。

ThreadLocalMap 的Entry源码:

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
	......
private Entry[] table;

存数据的类对象继承了弱引用,类似HashMap,但没有next,不是链表结构,采用数组结构

在这里插入图片描述

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

每一个ThreadLocal对象都有一个threadLocalHashCode,在插入过程中,根据这个hash值,定位到table中的位置i,如果当前位置是空的,就初始化一个Entry对象放在位置i上;如果不为空且key对象正好是传参的key对象,就刷新Entry中的value;

Thread init部分源码:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
			.......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

		........
}

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。如果线程的inheritThreadLocals变量不为空,而且父线程的inheritThreadLocals也存在,那么就把父线程的inheritThreadLocals给当前线程的inheritThreadLocals。

ThreadLocal实现父子线程数据共享
InheritableThreadLocal可实现

1、在创建 InheritableThreadLocal 对象的时候赋值给线程的 t.inheritableThreadLocals 变量
2、在创建新线程的时候会 check 父线程中 t.inheritableThreadLocals 变量是否为 null,如果不为 null 则 copy (浅拷贝)一份数据到子线程的 t.inheritableThreadLocals 成员变量中去
3、InheritableThreadLocal 重写了 getMap(Thread) 方法,所以 get 的时候,就会从 t.inheritableThreadLocals 中拿到 ThreadLocalMap 对象,从而实现了可以拿到父线程 ThreadLocal 中的值

存在问题

内存泄漏

Entry的key被设计成了弱引用

在这里插入图片描述
ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

解决办法:在代码的最后使用remove即可;在使用的最后用remove把值清空就好了。

为何key设计成弱引用?

key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。

是因为用强引用的话,如果使用ThreadLocal变量的某个线程长时间不中止,就算这个ThreadLocal对象超过了它应当的存在的生命周期,还是不能被回收,出现短生命周期对象被长生命周期引用所指向的情况,出现内存泄漏

参考文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Swing_zzZ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值