ThreadLocal

使用场景

使用场景是在于同一个类,但是会开多个线程执行,但是每一个线程可以保持不同的变量状态。

做法如上图,线程类Thread有成员变量ThreadLocal.ThreadLocalMap,用来存储该线程中的所有的ThreadLocal变量,初始化是一个Entry数组。

内存泄漏

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

Entry继承于WeakReference,简单说一下四种引用。强引用,就是我们常规使用的new出来一个对象,这时候会有变量建立具体的对象联系。软引用,适用于cache类型的变量,当jvm内存不够时会释放该引用。弱引用,只要发生gc就会回收。虚引用,没有很深刻的体会。

 SoftReference<Dog> softReference = new SoftReference<Dog>(new Dog("dd"));while (true){if(softReference.get() == null){System.out.println("null");break;}else {System.out.println("ok");}System.gc();}ok
ok
ok
...
WeakReference<Dog> weakReference = new WeakReference<Dog>(new Dog("dd"));while (true){if(weakReference.get() == null){System.out.println("null");break;}else {System.out.println("ok");}System.gc();}
ok
null

Entry中的key设置为虚引用,那么gc时候会被回收,此时ThreadLocal进行清理的时候可以根据key是否为null进行判断清除,防止内存泄漏。

但是还是要调用remove函数,这个在线程池中如果不执行的话会造成内存泄漏,因为线程不进行回收,那么ThreadLocalMap中会一直存在这些Entry,同时不进行remove的话就会一直占用内存。

Hash原理

获取线程对应的ThreadLocal,是应用hashcode在Map中定位,如果发生hash冲突使用的是线性寻地址法,即往下一位找,这种冲突方法有概率会导致死循环。所以如果变量过多,冲突很多,定位较慢。

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}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();}

FastThreadLocal

使用场景

不同于JDK自带的ThreadLocal,如果Thread是使用的FastThreadLocalThread,那么自带有private InternalThreadLocalMap threadLocalMap,那么如果类中有用到FastThreadLocal会从threadLocalMap中获取,netty中每一个FastThreadLocal都有全局唯一的index,所以是常数级从数组中定位获取内容,并且在set的同时会将该FastThreadLocal放到threadLocalMapindex0Set<FastThreadLocal<?>>上,这样垃圾清理会比较简单和快捷。

构造函数

   public FastThreadLocal() {index = InternalThreadLocalMap.nextVariableIndex();cleanerFlagIndex = InternalThreadLocalMap.nextVariableIndex();}

由该函数保证了index的全局唯一性

    public static int nextVariableIndex() {int index = nextIndex.getAndIncrement();if (index < 0) {nextIndex.decrementAndGet();throw new IllegalStateException("too many thread-local indexed variables");}return index;}

set方法

    public final void set(V value) {// 如果设置的非“空”if (value != InternalThreadLocalMap.UNSET) {// 获取存储的Map对象InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();// 如果当前设置的index上是unset(即我们已经开始污染使用这个Map了),进行注册清理操作(保证内存清理)if (setKnownNotUnset(threadLocalMap, value)) {registerCleaner(threadLocalMap);}} else {// 如果设置UNSET,则进行清理操作remove();}}

UNSET是一个new Object()对象,一方面用来填充整个空的Map,另一方面也是一个判断是否使用的标志。

    public static InternalThreadLocalMap get() {Thread thread = Thread.currentThread();if (thread instanceof FastThreadLocalThread) {// 直接会从FastThreadLocalThread中的成员变量中获取InternalThreadLocalMapreturn fastGet((FastThreadLocalThread) thread);} else {// ThreadLocal<InternalThreadLocalMap>,用JDK的ThreadLocal代存InternalThreadLocalMapreturn slowGet();}}
  private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {// 数组定位进行替换,判断原来的位置是否没用过,即是否为unsetif (threadLocalMap.setIndexedVariable(index, value)) {// 存放到数组[0]上的set中,方便回收addToVariablesToRemove(threadLocalMap, this);return true;}return false;}public boolean setIndexedVariable(int index, Object value) {Object[] lookup = indexedVariables;if (index < lookup.length) {Object oldValue = lookup[index];lookup[index] = value;return oldValue == UNSET;} else {expandIndexedVariableTableAndSet(index, value);return true;}}private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);Set<FastThreadLocal<?>> variablesToRemove;// 判断v有没有设置为set,一般是首次会执行这个if (v == InternalThreadLocalMap.UNSET || v == null) {variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);} else {// 已经设置过了variablesToRemove = (Set<FastThreadLocal<?>>) v;}// 直接在set中加入当前的FastThreadLocalvariablesToRemove.add(variable);}

注册到ObjectClean中,保证会被清理内存

    private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {Thread current = Thread.currentThread();if (FastThreadLocalThread.willCleanupFastThreadLocals(current) ||threadLocalMap.indexedVariable(cleanerFlagIndex) != InternalThreadLocalMap.UNSET) {return;}// removeIndexedVariable(cleanerFlagIndex) isn't necessary because the finally cleanup is tied to the lifetime// of the thread, and this Object will be discarded if the associated thread is GCed.threadLocalMap.setIndexedVariable(cleanerFlagIndex, Boolean.TRUE);// We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released// and FastThreadLocal.onRemoval(...) will be called.ObjectCleaner.register(current, new Runnable() {@Overridepublic void run() {remove(threadLocalMap);// It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once// the Thread is collected by GC. In this case the ThreadLocal will be gone away already.}});}

ObjectCleaner

这个和ThreadDeathWatcher的监控大致原理是相似的,都是开启一个监控守护线程进行for循环拉取任务,只是该类没有取消任务,所以直接一个并发安全的set就足够。

注册函数将需要进行清理的object对象设为虚引用,并保存了清理任务,放入到任务集中,如果未开启监控线程就开启。

监控线程死循环拿出虚引用队列,如果有引用拿到,说明该对象已经被gc,此时执行清理任务,如果无任务了就关闭线程。反之继续。

这么做能保证内存释放,即使是使用JDK的ThreadLocal,因为也是对象Map。

/*** Allows a way to register some {@link Runnable} that will executed once there are no references to an {@link Object}* anymore.*/
public final class eObjectCleaner {private static final int REFERENCE_QUEUE_POLL_TIMEOUT_MS =max(500, getInt("io.netty.util.internal.ObjectCleaner.refQueuePollTimeout", 10000));// Package-private for testingstatic final String CLEANER_THREAD_NAME = ObjectCleaner.class.getSimpleName() + "Thread";// This will hold a reference to the AutomaticCleanerReference which will be removed once we called cleanup()private static final Set<AutomaticCleanerReference> LIVE_SET = new ConcurrentSet<AutomaticCleanerReference>();private static final ReferenceQueue<Object> REFERENCE_QUEUE = new ReferenceQueue<Object>();private static final AtomicBoolean CLEANER_RUNNING = new AtomicBoolean(false);private static final Runnable CLEANER_TASK = new Runnable() {@Overridepublic void run() {boolean interrupted = false;for (;;) {// Keep on processing as long as the LIVE_SET is not empty and once it becomes empty// See if we can let this thread complete.while (!LIVE_SET.isEmpty()) {final AutomaticCleanerReference reference;try {// 从虚引用的队列中获取引用,这个是只有对象被回收才会放到这个队列中,能获取得到,说明该引用已经被gcreference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove(REFERENCE_QUEUE_POLL_TIMEOUT_MS);} catch (InterruptedException ex) {// Just consume and move oninterrupted = true;continue;}if (reference != null) {try {// 执行引用的清理任务,从而保证gc后也能清理reference.cleanup();} catch (Throwable ignored) {// ignore exceptions, and don't log in case the logger throws an exception, blocks, or has// other unexpected side effects.}// 从任务集中去除引用LIVE_SET.remove(reference);}}CLEANER_RUNNING.set(false);// Its important to first access the LIVE_SET and then CLEANER_RUNNING to ensure correct// behavior in multi-threaded environments.if (LIVE_SET.isEmpty() || !CLEANER_RUNNING.compareAndSet(false, true)) {// There was nothing added after we set STARTED to false or some other cleanup Thread// was started already so its safe to let this Thread complete now.break;}}if (interrupted) {// As we caught the InterruptedException above we should mark the Thread as interrupted.Thread.currentThread().interrupt();}}};/*** Register the given {@link Object} for which the {@link Runnable} will be executed once there are no references* to the object anymore.** This should only be used if there are no other ways to execute some cleanup once the Object is not reachable* anymore because it is not a cheap way to handle the cleanup.*/public static void register(Object object, Runnable cleanupTask) {AutomaticCleanerReference reference = new AutomaticCleanerReference(object,ObjectUtil.checkNotNull(cleanupTask, "cleanupTask"));// Its important to add the reference to the LIVE_SET before we access CLEANER_RUNNING to ensure correct// behavior in multi-threaded environments.// 任务集内容,要保证并发安全LIVE_SET.add(reference);// Check if there is already a cleaner running.// 如果running标志没开启,CAS操作进行开启一个守护线程执行if (CLEANER_RUNNING.compareAndSet(false, true)) {final Thread cleanupThread = new FastThreadLocalThread(CLEANER_TASK);cleanupThread.setPriority(Thread.MIN_PRIORITY);// Set to null to ensure we not create classloader leaks by holding a strong reference to the inherited// classloader.// See:// - https://github.com/netty/netty/issues/7290// - https://bugs.openjdk.java.net/browse/JDK-7008595AccessController.doPrivileged(new PrivilegedAction<Void>() {@Overridepublic Void run() {cleanupThread.setContextClassLoader(null);return null;}});cleanupThread.setName(CLEANER_THREAD_NAME);// Mark this as a daemon thread to ensure that we the JVM can exit if this is the only thread that is// running.cleanupThread.setDaemon(true);cleanupThread.start();}}public static int getLiveSetCount() {return LIVE_SET.size();}private ObjectCleaner() {// Only contains a static method.}// 继承软引用,这里Object是thread,用来判断thread是否被回收private static final class AutomaticCleanerReference extends WeakReference<Object> {// 存下任务private final Runnable cleanupTask;AutomaticCleanerReference(Object referent, Runnable cleanupTask) {super(referent, REFERENCE_QUEUE);this.cleanupTask = cleanupTask;}void cleanup() {cleanupTask.run();}@Overridepublic Thread get() {return null;}@Overridepublic void clear() {LIVE_SET.remove(this);super.clear();}}
}

remove方法

    public final void remove(InternalThreadLocalMap threadLocalMap) {if (threadLocalMap == null) {return;}// lookup[index] = UNSET;制定位置置为unset,去除value强引用Object v = threadLocalMap.removeIndexedVariable(index);// set.remove(this),去除FastTheadLocal的强引用removeFromVariablesToRemove(threadLocalMap, this);if (v != InternalThreadLocalMap.UNSET) {try {// 可以重写,做一些自己想做的事情。。onRemoval((V) v);} catch (Exception e) {PlatformDependent.throwException(e);}}}// 从set中取出所有的FastThreadLocal执行remove,并且最后将Map置为空,all overpublic static void removeAll() {InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();if (threadLocalMap == null) {return;}try {Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);if (v != null && v != InternalThreadLocalMap.UNSET) {@SuppressWarnings("unchecked")Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;FastThreadLocal<?>[] variablesToRemoveArray =variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]);for (FastThreadLocal<?> tlv: variablesToRemoveArray) {tlv.remove(threadLocalMap);}}} finally {InternalThreadLocalMap.remove();}}

get方法

    public final V get() {InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();Object v = threadLocalMap.indexedVariable(index);if (v != InternalThreadLocalMap.UNSET) {return (V) v;}// 利用重写的初始化函数进行初始化V value = initialize(threadLocalMap);registerCleaner(threadLocalMap);return value;}

对比

  • 都是Thead自己存储自己的TheadLocal
  • JDK的存储使用线性探测法的Map,数量大容易造成冲突,性能下降很快,并且会有内存泄漏的风险。
  • FastTheadLocal快的原因改进了存储方式,全局唯一index来标志一个ftl,当然这样如果全局ftl很多会造成空间浪费,这是一种空间换时间的方式。同时它会进行内存监控清理防止内存泄漏。
  • 个人认为在TheadLocal不多的情况下其实两种性能差不多(因为JDK自身不会hash冲突),但是Ftl更能保证内存不泄漏,所以JDK调用的时候记得remove

reference

https://www.jianshu.com/p/3fc2fbac4bb7

转载于:https://www.cnblogs.com/GrimReaper/p/10385325.html

FastThreadLocal相关推荐

  1. FastThreadLocal吞吐量居然是ThreadLocal的3倍

    目前关于FastThreadLocal的很多文章都有点老有点过时了(本文将澄清几个误区),很多文章关于FastThreadLocal介绍的也不全,希望本篇文章可以带你彻底理解FastThreadLoc ...

  2. 吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?

    欢迎关注方志朋的博客,回复"666"获面试宝典 1 FastThreadLocal的引入背景和原理简介 既然jdk已经有ThreadLocal,为何netty还要自己造个FastT ...

  3. 惊:FastThreadLocal吞吐量居然是ThreadLocal的3倍!!!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 接着上次手撕面试题ThreadLocal!!!面试官一听,哎呦不错 ...

  4. ThreadLocal的原理和FastThreadLocal的优势

    一.ThreadLocal的编写测试 1.只需定义一个静态全局的ThreadLocal变量,然后在线程的执行方法里面,对这个对象的某个方法,set后,同一个线程get,能正常取出数据. 2.线程池使用 ...

  5. FastThreadLocal原理

    一.ThreadLocal的原理以及存在的问题 a. 每个线程内部维护了一个ThreadLocal.ThreadLocalMap类型的变量 b. ThreadLocalMap 的 key 为 Thre ...

  6. Netty技术细节源码分析-FastThreadLocal源码分析

    本文是该篇的修正版 本文的github地址:点此 Netty 的 FastThreadLocal 源码解析 该文中涉及到的 Netty 源码版本为 4.1.6. Netty 的 FastThreadL ...

  7. 关于netty的FastThreadLocal的思考

    Netty版本4.1.6. 与jdk的ThreadLocal相比,netty的FastThreadLoacl具有更高的读写性能,如何针对原生的ThreadLocal进行优化. 准备采用netty的Fa ...

  8. Netty 的 FastThreadLocal 到底快在哪里

    其实呢,追踪一下常用的 Spring 等框架,会发现正常运转的情况下,一个线程最多也就三四十个 ThreadLocal 变量,那么,Netty 为何还要大费周章搞一个 FastThreadLocal ...

  9. 吊打 ThreadLocal!

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://blog.csdn.net/mycs2012/article/details/90898128 FastThr ...

最新文章

  1. 产品经理如何在技术人员面前更有说服力?
  2. 基于SSM实现保健院管理系统
  3. 这些大佬,真的牛逼了!
  4. 快消行业指的是哪些?
  5. ASP.NET Button控件的UseSubmitBehavior属性引发的血案
  6. “悟道”公开课第二讲丨如何优化大模型输出结果
  7. 软件工程概论 课堂练习 第2次作业3【酒店预订】
  8. 47、Windows驱动程序模型笔记(五),内存管理
  9. JS高级-JQ初识-元字符-正则表达式
  10. CRM My Opportunity max hit的技术实现
  11. 高等数学上-赵立军-北京大学出版社-题解-练习2.3
  12. 华为p4支持鸿蒙功能吗_什么样的手机可以刷鸿蒙系统?看看你的手机支持吗?...
  13. OpenGL仿作橡皮筋技术
  14. mysql接口教程_接口测试教程 - xmysql 介绍
  15. C++ const修饰指针变量的位置不同代表的意义
  16. linux内存管理之DMA
  17. 无法解析的外部符号:GetWindowThreadProcessId/EnumWindow
  18. 程序员最不想让你知道的尴尬瞬间,看完我眼睛都绿了
  19. 为了软银的万亿物联网 ARM取消了一笔企业预授权费用
  20. 关于HP笔记本的老毛桃装系统。

热门文章

  1. 当 Android 开发者遇见 TensorFlow
  2. Ransomware CryptXXX Analysis
  3. 安装mysql5.7
  4. JZOJ 5396. 【NOIP2017提高A组模拟10.6】Blocks
  5. thinkphp后台_【帮转】PS4中文社区php后台工程师志愿者/实习生招募
  6. 写在岁末 -- 程序员的人生并非那么容易,努力向前奔跑吧!
  7. vue 音乐盒app_VBox 一款基于vue开发的音乐盒 序章
  8. s matlab toolbox,Matlab Robotic Toolbox工具箱学习笔记(一 )
  9. hdu5399(找规律。。。)
  10. 奖学金pascal程序