摘要:通过将保存在中,每个线程都会拥有属于自己的,代码如下所示然后你就可以安心地调用了,不用考虑线程安全问题。这样设计的好处就是,当线程死掉之后,没有强引用,方便收集器回收。
前言
想必大家都对Threadlocal很熟悉吧,今天我们就一起来深入学习一下。Threadlocal我更倾向于将其翻译成线程局部变量。它有什么用处呢?Threadlocal对象通常用于防止对可变的单实例变量或全局变量进行共享。在spring中,通过将事务上下文保存在静态的threadlocal中,当框架代码需要判断当前运行的是哪一个事务时,只需要从ThreadLocal对象中获取事务上下文,这种机制很方便,避免了在每个方法都要传递上下文信息。
一个小例子众所周知,SimpleDateFormat不是一个线程安全的类,在多线程环境下使用同一个实例是不正确的。通过将SimpleDateFormat保存在Threadlocal中,每个线程都会拥有属于自己的SimpleDateFormat,代码如下所示:
public class DateFormatUtil { public static ThreadLocaldataFormatlocal = new ThreadLocal () { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; public static SimpleDateFormat getInstance() { return dataFormatlocal.get(); } }
然后你就可以安心地调用了,不用考虑线程安全问题。当然你也可以在每个线程内部调用new SimpleDateFormat(),但现实情况是大部分开发可能并不知道它是线程不安全的,并且将其封装成一个单例的工具类。至于如何使用,全凭各位读者爱好,这个例子主要用来演示Threadloca的基本用法。
脑洞大开---自己设计个threadlocal通过之前的演示,我们已经大致知道threadlocal怎么用,并且清楚threadlocal保证了每个线程都有一个局部变量副本。如果以此为需求,让我们自己去设计,会是什么样子的呢?
static Mapthreadlocal = new HashMap ();
这就是我设计的....好吧(╯▽╰),原谅我水平有限....在这个hashMap中,threadId为key,Object为value,也是可以实现Threadlocal的。那么我们现在要来考虑几个问题。jdk是这样设计的吗?这样设计很low,但是low在哪了?
答:大师们当然不是这样设计了。如果这样设计,每个线程的变量都会永久的保存在hashMap中,存在内存泄漏。
low的写法我们已经见过了,现在我们一起来分下jdk是如何设计的,本文引用jdk1.8。
让我们看下threadlocal的结构图:
类核心方法set、get、initialValue、setInitialValue、remove,后面主要围绕着这几个方法介绍。
类核心变量threadLocalHashCode,nextHashCode,HASH_INCREMENT,其中nextHashCode和HASH_INCREMENT都是静态的,所以对于一个threadlocal对象,成员变量只有一个threadLocalHashCode,这是一个自定义的hash函数,主要为了减少散列桶的冲突(不是本文重点,好奇的可以看下这篇博客https://www.cnblogs.com/ilell...)。
那到底Threadlocal将变量存到哪了呢?眼尖的同学肯定已经发现了ThreadLocalMap,再来看下ThreadlocalMap的结构:
相信读过hashMap源码同学的一定会觉得非常眼熟,ThreadlocalMap的主体也是Entry[],有resize()和rehash(),区别仅仅就是在于没有使用链表结构和红黑树来处理散列冲突了。Entry的结构我们也很有必要了解一下:
Entry继承了一个弱引用,这里简单介绍下弱引用的知识。弱引用是用来描述非必需对象的,被弱引用关联的对象只能生存到下次垃圾收集发生之前。无论当前内存是否足够,都会回收掉只被弱引用关联的对象。很多面试官都喜欢问Threadlocal是怎么发生内存泄漏的,其实就是在问这个,这个需要我们对Threadlocal有一个整体的了解才能明白,所以放在最后讲。
当我们看完Threadlocal的结构了,我们发现ThreadlocalMap是用来存储的数据结构,那它是怎么和线程关联起来的呢?这里我们开始研究Threadlocal.set()方法.
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
步骤1:获取当前线程
步骤2:通过当前线程获取ThreadLocalMap
步骤3:如果map不为null,将当前线程和value放到map中,否则创建一个map。
如何和线程绑定的玄机就在getMap(t)中。
ThreadLocalMap getMap(Thread t) { return t.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有一个内部类ThreadlocalMap,对于任何一个线程都会有唯一的一个ThreadlocalMap来对应,而这个map实际并不存储在Threadlocal中,而是存在Thread当中,只不过由Threadlocal暴露了一套api来维护Thread的ThreadLocalMap。这样设计的好处就是,当线程死掉之后,ThreadLocalMap没有强引用,方便收集器回收。
继续来看get()
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
如果前面的流程看懂了,这就很简单了。当ThreadLocalMap为null或者Entry为null的时候将会调用setInitialValue();
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
流程也很简单,调用initialValue()初始化一个值,获取当前线程的ThreadLocalMap,后面的流程之前都已经介绍过了,这里不再重复,我们主要来看下initialValue()
protected T initialValue() { return null; }
请注意,protected修饰,return null;这是一个初始化值得方法,也就意味着如果业务允许的话,需要我们自己实现initialValue();
ThreadLocal的remove实现
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap的remove实现
private void remove(ThreadLocal key) { 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)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
都非常简单,就留给大家自己看了~。~.
Threadlocal容易发生内存泄露?先回顾一下ThreadlocalMap的结构
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{ /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } ..... ..... }
然后套用网上的一张图(实线表示强引用,虚线表示虚引用)
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄露。其实jdk已经考虑到了这种情况,ThreadLocalMap的genEntry函数或者set函数会去遍历将key为null的给移除掉,但这明显不是所有情况都成立的,所以需要调用者自己去调用remove函数,手动删除掉需要的threadlocal,防止内存泄露。然后jdk并不建议在栈内声明threadlocal,而是建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
放图证明这是jdk说的~~
再不睡觉,就天明了..
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69665.html
摘要:具体怎么实现的呢,思想其实特别简单,我们在深入理解中的变量上一文的最后有提起过,就是创建一个全局字典,然后将线程或者协程标识符作为,相应线程或协程的局部数据作为。 在上篇我们看到了 ThreadLocal 变量的简单使用,中篇对python中 ThreadLocal 的实现进行了分析,但故事还没有结束。本篇我们一起来看下Werkzeug中ThreadLocal的设计。 Werkzeug...
摘要:在深入理解中的变量上中我们看到的引入,使得可以很方便地在多线程环境中使用局部变量。特别需要注意的是,基类的并不会屏蔽派生类中的创建。到此,整个源码核心部分已经理解的差不多了,只剩下用来执行清除工作。 在 深入理解Python中的ThreadLocal变量(上) 中我们看到 ThreadLocal 的引入,使得可以很方便地在多线程环境中使用局部变量。如此美妙的功能到底是怎样实现的?如果你...
摘要:虽然类名中带有字样,但是实际上并不是接口的子类。是弱连接接口,这意味着如果仅有指向某一类,其任然有可能被回收掉。这里使用弱连接的意义,是为了防止业务代码中置空对象,但是由于存在连接可达,所以仍然无法回收掉该对象的情况发生。 零 前期准备 0 FBI WARNING 文章异常啰嗦且绕弯。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 T...
摘要:方法,删除当前线程绑定的这个副本数字,这个值是的值,普通的是使用链表来处理冲突的,但是是使用线性探测法来处理冲突的,就是每次增加的步长,根据参考资料所说,选择这个数字是为了让冲突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学...
阅读 561·2023-04-25 21:29
阅读 1090·2023-04-25 21:27
阅读 1029·2021-11-25 09:43
阅读 1052·2021-09-29 09:43
阅读 3596·2021-09-03 10:30
阅读 2833·2019-08-29 15:26
阅读 2780·2019-08-29 12:52
阅读 1725·2019-08-29 11:10