谈一谈不常见却又不可少的ThreadLocal

在写ThreadLocal之前,需要先巩固下一点相关知识:Java内存模型及共享变量的可见性。

内存模型中所有变量存储在主内存中,当一个线程中要使用某个变量时,需要从主内存复制该变量到其线程内才能操作,此时线程中操作的是主内存变量的副本,操作完成后再刷回主内存。刷回的实质就是变量赋值

如果多个线程访问同一个变量时,每个线程都具有一个副本,操作完毕后都会刷回主内存,刷回时间存在先后,则赋值有先后,当然后者会覆盖前者,这是造成可见性问题的次要原因。

引入以上知识点后,再来说明ThreadLocal。一个线程想使用某个变量,于是从主内存复制该共享变量到线程内部中。使用完毕后想再下次再次使用该变量时,得到的变量副本是上次使用的副本,而不是从主内存的变量再次复制过来的副本,并且不想让其他线程影响到该变量。这就是ThreadLocal的目的,其实现不是通过共享变量这种方式实现的,详细内容下面介绍

目的很明确,但是身处JAVA内存模型中要遵循内存模型规范,下面看看JDK是如何即满足内存模型规范,又满足ThreadLocal目的。

满足内存模型

这点很简单,就是你该怎么样还怎么样,仍然受你管辖,该复制就复制,该刷回就刷回,不可见还是会造成不可见。

满足ThreadLocal目的

多个线程都能访问的变量才叫共享变量,如果控制变量的访问方式,使其他线程线程不能访问就可以了。控制方式就是将线程与变量的一一对应,将该变量的访问入口控制到只有该线程即可,JDK中的做法就是让线程持有这个变量(绑定到线程本身)

线程可以绑定变量,但是并不知道需要绑定多少个,于是将这个存储功能还是交给专门的数据结构—>Map。并且还专门设计了一个用来访问这个Map的工具,这个工具就是ThreadLocal。并且这个Map的key为ThreadLocal实例的引用地址,value存储真正的变量。

这样设计就到达目的了。ThreadLocal构建时接收个泛型告诉你存储的变量是一个对象类型。

ThreadLocal设计

核心Map设计

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

这里Map的Entry被设计为弱引用。

内存存储图示如下

弱引用WeakReference

WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。

这里使用弱引用的原因:ThreadLocal目的就是达到变量只能自己访问别的线程不能访问的目的,Map设计的key为ThreadLocal实例的引用地址,value为变量,当线程存在而ThreadLocal实例被回收时,Map中的value还是存在的(原因后续说明)致使该变量的可达性分析失败,从而导致此阶段的内存泄露。当设计为弱引用后,如果key被销毁了(强引用销毁),那么value就处于可被回收状态,从而避免内存泄露。

临时内存泄露图示

Map的持有状态

在Thread类定义中这样定义ThreadLocalMap

public class Thread implements Runnable {  ...  ThreadLocal.ThreadLocalMap threadLocals = null;  ...}

也就是ThreadLocalMap是线程类Thread持有的对象,一个线程持有一个ThreadLocalMap,只有线程存在,则ThreadLocalMap必存在。这也是ThreadLocal对象销毁后,value还存在的原因,ThreadLocalMap还被Thread强引用。只有线程销毁时,ThreadLocalMap才会随之销毁。

ThreadLocal的使用

private static final ThreadLocal threadLocal = new ThreadLocal<>();  // 接收泛型
threadLocal.set(1);  // 对应泛型
Integer var=threadLocal.get();

ThreadLocal底层原理

设置变量 Set

public void set(T value) {    // 获取当前线程    Thread t = Thread.currentThread();// 从当前线程中获取一个ThreadLocalMap实例    ThreadLocalMap map = getMap(t);// map存在则放入key,value  key为当前ThreadLocal对象引用    if (map != null)        map.set(this, value);    // map不存在则构建,同样放入key,value      else        createMap(t, value);}

ThreadLocalMap实例的获取就是从Thread中获取的,也就是上面的持有状态,拿到之后就可以向Map结构中存储key,value了,前面也说过了,这里key存在的ThreadLocal实例的引用地址,value存在的变量的引用地址。一个线程可以存储多个ThreadLocal实例。

getMap(Thread t)

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

就是获取当前线程持有的ThreadLocalMap

createMap(Thread t, T firstValue)

void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }

给当前线程初始化ThreadLocalMap实例,并set初始值。key为ThreadLocal实例的引用地址

获取变量Get

public T get() {    Thread t = Thread.currentThread();  //① 获取当前线程t    ThreadLocalMap map = getMap(t); //②从当前线程中获取一个ThreadLocalMap    if (map != null) { //③ ThreadLocalMap不为null则从map中获取value ;             ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();//④ThreadLocalMap为null则调用setInitialValue() 返回—初始化的值}

在最后一个return之前的代码就是从当前线程的ThreadLocalMap中通过Map的get方法获取变量引用的过程。这是Map的基本用法。

存在以下情况则需要获取初始的默认值,这是一个对外开放的功能,就是可以指定ThreadLocal对应的变量的初始默认值,默认为null,可以被重写

  • ThreadLocalMap未实例化
  • ThreadLocalMap已实例化,但是还没有Set变量

设置默认的初始值

protected T initialValue() {    return null;}private T setInitialValue() {    T value = initialValue(); //① 初始化为null    Thread t = Thread.currentThread(); //② 获取当前线程    ThreadLocalMap map = getMap(t); //③ 从当前线程中获取一个ThreadLocalMap    if (map != null)        map.set(this, value); //④ ThreadLocalMap不为null,则存储key=this,value=value    else        createMap(t, value);//⑤ ThreadLocalMap为null,则为当前线程创建一个ThreadLocalMap并初始化值    return value;}void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}

代码流程也很简单,只是为了功能优化了,进行的统一封装

  • ThreadLocalMap的惰性实例化
  • 获取ThreadLocalMap时需要Thread,而ThreadLocal只是访问控制工具,于是需要打通Thread来获取ThreadLocalMap

移除

public void remove() {    ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null)        m.remove(this);}

主动移除是个好的处理方式,在不使用变量时应该主动移除

到这里ThreadLocal本身的功能已经介绍完了,可以理解为变量访问工具,这个变量的访问被控制到只能当前线程有权限访问,其他线程无权限。

代码示例

简单示例

package cn.tinyice.demo.thread;import java.util.Random;import java.util.concurrent.TimeUnit;/*** ThreadLocalDemo** @author Tinyice*/public class ThreadLocalDemo {    private static final ThreadLocal threadLocal = new ThreadLocal<>();    private static final Random random=new Random();    /**     * 内存模型示例     */    private int mod=0;    public ThreadLocalDemo(int mod) {        this.mod = mod;    }    public int getMod() {        return mod;    }    public void add() {        mod+=1;        // 每个线程的值是不一样的        threadLocal.set(get() + random.nextInt(10));    }    public Integer get() {        Integer integer = threadLocal.get();        return null == integer ? 0 : integer;    }    public static void main(String[] args) {        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0);        for (int i = 0; i < 1000; i++) {            new ThreadLocalThread(threadLocalDemo).start();        }        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("------------------------------------------------");        System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod()));    }}class ThreadLocalThread extends Thread {    private ThreadLocalDemo threadLocalDemo;    public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) {        this.threadLocalDemo = threadLocalDemo;    }    @Override    public void run() {        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        threadLocalDemo.add();        System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get()));    }}

控制台:

threadId=853 threadLocal value=3threadId=848 threadLocal value=9threadId=808 threadLocal value=9threadId=121 threadLocal value=8------------------------------------------------threadId=1 threadLocal value=0 mod=999Process finished with exit code 0

mod=999 是可见性问题造成,而value值都不一样则说明不同线程的变量值不一样。

Spring示例

public abstract class TransactionSynchronizationManager {    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);    private static final ThreadLocal> resources = new NamedThreadLocal("Transactional resources");    private static final ThreadLocal> synchronizations = new NamedThreadLocal("Transaction synchronizations");    private static final ThreadLocal currentTransactionName = new NamedThreadLocal("Current transaction name");    private static final ThreadLocal currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");    private static final ThreadLocal currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");    private static final ThreadLocal actualTransactionActive = new NamedThreadLocal("Actual transaction active");...}
public final class DateTimeContextHolder {private static final ThreadLocal dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext");       ...}

本文源自:https://www.tinyice.cn/articles/126

作者:程序猿微录

threadlocal内存泄露_ThreadLocal原理解析相关推荐

  1. threadlocal内存泄露_ThreadLocal 简介

    本文转载于SegmentFault社区 作者:莫小点还有救 1. ThreadLocal简介 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的.如果想实现每一个线程都有自己的专属本地变量该如 ...

  2. threadlocal内存泄露_ThreadLocal用法详解和原理

    一.用法 ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量. 1.Thr ...

  3. java thread 内存泄露_Java ThreadLocal 内存泄露问题分析及解决方法。

    前言 在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露.强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这 ...

  4. threadlocal内存泄露_深入理解 ThreadLocal

    前言 上篇文章 https://juejin.im/post/5d712cedf265da03ea5a9ecf 中提到了获取线程的 Looper 是通过 ThreadLocal 来实现的: publi ...

  5. ThreadLocal 内存泄露的实例分析

    前言 之前写了一篇深入分析 ThreadLocal 内存泄漏问题是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例.分析问题的过程比结果更重要,理论结合实际 ...

  6. Introspector内存溢出的原理解析

    参考:https://blog.csdn.net/luman1991/article/details/52164953 JavaBeans Introspector是一个类,位置在Java.bean. ...

  7. C++内存泄露检测原理

    转自:http://hi.baidu.com/jasonlyy/item/9ca0cecf2c8f113a99b4981c 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨 ...

  8. ThreadLocal系列(二)-InheritableThreadLocal的使用及原理解析

    ThreadLocal系列之InheritableThreadLocal的使用及原理解析(源码基于java8) 上一篇:ThreadLocal系列(一)-ThreadLocal的使用及原理解析 下一篇 ...

  9. 03弱引用内存泄露和hash冲突-ThreadLocal详解-并发编程(Java)

    文章目录 1 问题 2 内存泄露 3 弱引用 4 问题分析 4.1 key为强引用 4.2 key为弱引用 4.3 内存泄漏的真正原因 4.4 为什么Entry 的key使用弱引用 5 hash冲突的 ...

最新文章

  1. Android 监听手机GPS打开状态
  2. .NET 6 Preview 3 发布
  3. Spark入门(十)之Distinct去重
  4. channelsftp的put_java实现sftp客户端上传文件以及文件夹的功能代码
  5. springboot 事务统一配置_Spring Boot实现分布式微服务开发实战系列(五)
  6. 简单 黑苹果dsdt教程_小米Ruby笔记本安装98%完美黑苹果教程
  7. Android 布局管理器 之 TableLayout
  8. 了解这12个概念,让你的JavaScript水平更上一层楼
  9. springcloud之bus消息总线
  10. 从零开始学习makefile(7) makefile的filter的作用
  11. H5 font标签及其属性
  12. 计算机DVD驱动禁用怎么恢复,设备管理器中找不到dvd驱动器 怎么恢复 - 驱动管家...
  13. Unity-两张图片叠加合成一张图片
  14. 基于 Amazon SageMaker 构建物流需求量预测解决方案
  15. 非常精美的唐诗,无与伦比哦
  16. c语言1076素数,九度OJ 1076:N的阶乘 题解
  17. unity | 写一个XML和用unity读取XML
  18. 分手后怎样才能让前任重回你身边
  19. RuoYi 若依平台登录密码忘记了-如何解决
  20. Babylon.js 深入

热门文章

  1. python找出函数最小值极其对应的自变量的值
  2. pytorch VIF(VIT 改)快了两倍
  3. 牛客华为机试第8题python
  4. 小米MixPath复现之旅
  5. 洛谷P2057 【SHOI2007】善意的投票
  6. InnoDB的MVCC如何解决幻读
  7. 小米今日正式进军越南市场 借助合作方铺渠道分销
  8. 从线上教育的如火如荼,反思传统培训行业的未来发展
  9. PHPExcel读取excel的多个sheet存入数据库
  10. 用例子说明MVC 设计模式(以Objective-C 实现)