ThreadLocal源码分析详解

ThreadLocal源码分析详解

在实际开发过程中用到ThreadLocal的地方不是很多,ThreadLocal除了系统源码的使用,很少在项目中用到,不过也还是有的。

ThreadLocal的作用就是做数据隔离,填充的数据只属于当前线程,变量的数据对其他线程而言是相对隔离的,在多线程的环境下,就是如何防止自己的变量被其他线程篡改

隔离的作用 使用场景

Spring事务的隔离级别

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

Spring 的事务主要是ThreadLocal和AOP实现的,每个线程自己的链接是靠ThreadLocal保存就好

底层实现原理

使用:

ThreadLocal<String> localName = new ThreadLocal();
localName.set("李四");
String name = LocalName.get();
localName.remove();

线程进来后先初始化一个可以泛型的ThreadLocal对象,之后这个线程只要在remove之前get,就可以拿到之前set的值

他是能做到线程间的数据隔离的,所以别的线程去get方法是没有办法拿到其他线程的值

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方法,就是获取当前线程的threadLocals变量

public class Thread implements Runnable {
      ……

    /* 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;
  
     ……

这里就可以知道他实现线程隔离的原理,每个线程都维护了自己的threadLocals变量,每个线程创建的时候,实际上数据是存储在自己线程的threadLocals变量里面,从而实现隔离

ThreadLocals的底层数据结构

通过源码可以发现,他并没有实现Map接口,而他的Entry是继承WeakReference(弱引用),不存在链表,底层是数组

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

结构图示:

img

在开发过程中,一个线程是可以有多个ThreadLocal来存放多个不同类型的对象的,但是他们都将放到当前线程的ThreadLocalMap里,所以使用数组

解决Hash冲突
private void set(ThreadLocal<?> key, Object value) {
           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();
        }

ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个ThreadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位table中的位置然后判断一下:如果当前位置为空,就初始化一个Entry对象放在该位置

if (k == null) {
    replaceStaleEntry(key, value, i);
    return;
}

如果不为空,这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value

if (k == key) {
    e.value = value;
    return;
}

如果不为空,key的值不等于Entry,就找下一个空位置

这样,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象的key是否和get的key一样,不一致就判断下一个位置

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

 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
// get的时候一样是根据ThreadLocal获取到table的i值,然后查找数据拿到后会对比key是否相等  if (e != null && e.get() == key)。
            while (e != null) {
                ThreadLocal<?> k = e.get();
              // 相等就直接返回,不相等就继续查找,找到相等位置。
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

ThreadLocal对象的存储位置

在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值其实也是被线程实例持有,它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。

ThreadLocal实现共享数据

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,在主线程中创建一个InheritableThreadLocal的对象,然后在子线程中得到这个InheritableThreadLocal实例设置的值,也就是父子线程数据传递的问题
在这里插入图片描述

Thread源码中,Thread.init初始化创建的时候,如果线程的InheritableThreadLocal变量不为空,但是父线程的InheritableThreadLocal存在,那么就把父线程的InheritableThreadLocal给当前线程的InheritableThreadLocal

ThreadLocal存在的问题:内存泄漏

在这里插入图片描述

ThreadLocal在保护的时候会把自己的key存储在ThreadLocalMap中,正常情况下,key和value应该被外界强引用才对,但是现在key被设计成WeakReference弱引用

弱引用

他有很短暂的生命周期,在垃圾回收器线程扫描他所管辖的内存区域的时候,一旦发现有弱引用对象,不管内存空间足够与否,都会回收

如果ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。

解决:

配合try、finally块,在使用完成后用remove值清空就好了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal码是Java中一个关键的类,它提供了一种在多线程环境下实现线程本地变量的机制。在JDK 8之前和之后,ThreadLocal的内部结构有所变化。ThreadLocal码分为两部分:ThreadLocal类和ThreadLocalMap类。 ThreadLocal类是一个泛型类,它包含了两个核心方法:set()和get()。set()方法用于将一个值与当前线程关联起来,get()方法用于获取当前线程关联的值。 ThreadLocalMap类是ThreadLocal的内部类,它用于存储每个线程的本地变量。在JDK 8之前,ThreadLocalMap是通过线性探测法解决哈希冲突的,每个ThreadLocal对象都对应一个Entry对象,Entry对象包含了ThreadLocal对象和与之关联的值[2]。 在JDK 8之后,ThreadLocalMap的实现方式发生了改变。使用了类似于HashMap的方式,采用了分段锁的机制来提高并发性能。每个线程维护一个ThreadLocalMap对象,其中的Entry对象也是采用链表的形式来解决哈希冲突。 总结起来,ThreadLocal码主要由ThreadLocal类和ThreadLocalMap类组成。ThreadLocal类提供了set()和get()方法来管理线程本地变量,而ThreadLocalMap类则负责存储每个线程的本地变量,并解决哈希冲突的问题。 史上最全ThreadLocal 详解 ThreadLocal分析_02 内核(ThreadLocalMap) 【JDK码】线程系列之ThreadLocal 深挖ThreadLocal ThreadLocal原理及内存泄露预防 ThreadLocal原理详解——终于弄明白了ThreadLocal ThreadLocal使用与原理 史上最全ThreadLocal 详解ThreadLocal分析,主要有ThreadLocal码以及ThreadLocal的内部结构在jdk8前后的变化。 使用方式非常简单,核心就两个方法set/get public class TestThreadLocal { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { threadLocal.set("aaa"); Thread.sleep(500); System.out.println("threadA:" threadLocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { threadLocal.set("bbb"); System.out.println("threadB:" threadLocal.get()); } }).start(); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值