本文转载于SegmentFault社区

作者:莫小点还有救


1. ThreadLocal简介

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?JDK中提供的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

再举个简单的例子:

比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来避免这两个线程竞争的。

2. ThreadLocal示例


相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。

import java.text.SimpleDateFormat;import java.util.Random;public class ThreadLocalExample implements Runnable{     // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本    private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));    public static void main(String[] args) throws InterruptedException {        ThreadLocalExample obj = new ThreadLocalExample();        for(int i=0 ; i<10; i++){            Thread t = new Thread(obj, ""+i);            Thread.sleep(new Random().nextInt(1000));            t.start();        }    }    @Override    public void run() {        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());        try {            Thread.sleep(new Random().nextInt(1000));        } catch (InterruptedException e) {            e.printStackTrace();        }        //formatter pattern is changed here by thread, but it won't reflect to other threads        formatter.set(new SimpleDateFormat());        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());    }}

Output:

Thread Name= 0 default Formatter = yyyyMMdd HHmmThread Name= 0 formatter = yy-M-d ah:mmThread Name= 1 default Formatter = yyyyMMdd HHmmThread Name= 2 default Formatter = yyyyMMdd HHmmThread Name= 1 formatter = yy-M-d ah:mmThread Name= 3 default Formatter = yyyyMMdd HHmmThread Name= 2 formatter = yy-M-d ah:mmThread Name= 4 default Formatter = yyyyMMdd HHmmThread Name= 3 formatter = yy-M-d ah:mmThread Name= 4 formatter = yy-M-d ah:mmThread Name= 5 default Formatter = yyyyMMdd HHmmThread Name= 5 formatter = yy-M-d ah:mmThread Name= 6 default Formatter = yyyyMMdd HHmmThread Name= 6 formatter = yy-M-d ah:mmThread Name= 7 default Formatter = yyyyMMdd HHmmThread Name= 7 formatter = yy-M-d ah:mmThread Name= 8 default Formatter = yyyyMMdd HHmmThread Name= 9 default Formatter = yyyyMMdd HHmmThread Name= 8 formatter = yy-M-d ah:mmThread Name= 9 formatter = yy-M-d ah:mm

从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。

上面有一段代码用到了创建 ThreadLocal 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法withInitial(),将Supplier功能接口作为参数。

private static final ThreadLocal formatter = new ThreadLocal(){        @Override        protected SimpleDateFormat initialValue(){            return new SimpleDateFormat("yyyyMMdd HHmm");        }    };

3. ThreadLocal原理


从 Thread类源代码入手。

public class Thread implements Runnable { ......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMap threadLocals = null;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ......}

从上面Thread类 源代码可以看出Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。

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);    }    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }

通过上面这些内容,我们足以通过猜测得出结论:最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key ,Object 对象为 value的键值对。

ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) { ......}

比如我们在同一个线程中声明了两个 ThreadLocal 对象的话,会使用 Thread内部都是使用仅有那个ThreadLocalMap 存放数据的,ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal 对象调用set方法设置的值。

ThreadLocalMap是ThreadLocal的静态内部类。

4. ThreadLocal 内存泄露问题


ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法

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

弱引用介绍:

如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。


- END -

threadlocal内存泄露_ThreadLocal 简介相关推荐

  1. threadlocal内存泄露_ThreadLocal原理解析

    谈一谈不常见却又不可少的ThreadLocal 在写ThreadLocal之前,需要先巩固下一点相关知识:Java内存模型及共享变量的可见性. 内存模型中所有变量存储在主内存中,当一个线程中要使用某个 ...

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

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

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

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

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

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

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

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

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

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

  7. 【java】ThreadLocal 内存泄漏 代码演示 实例演示

    1.概述 转载:ThreadLocal 内存泄漏 代码演示 实例演示 首先看文章:ThreadLocal内存泄露原因分析 相关文章: [高并发]ThreadLocal.InheritableThrea ...

  8. 深入分析 ThreadLocal 内存泄漏问题

    转载自 http://www.importnew.com/22039.html ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件 ...

  9. inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?

    在面试的时候,ThreadLocal作为高并发常用工具经常会被问到.而面试官比较喜欢问的问题有以下两个: 1.ThreadLocal是怎么实现来保证每个线程的变量副本的. 2.ThreadLocal的 ...

最新文章

  1. java methode_Java Method.getTypeParameters方法代码示例
  2. linux下 DNS配置过程『罗斌原创』
  3. 设计模式学习笔记(十六:桥接模式)
  4. 递归遍历文件夹,并添加到TreeView控件中
  5. CDN应用进阶 | 正确使用CDN 让你更好规避安全风险
  6. Java并行任务框架Fork/Join
  7. fatal error: Eigen3/Core: 没有那个文件或目录
  8. mybatis xml sql
  9. spss多元线性回归散点图_案例分析 | 多元线性回归及SPSS操作
  10. 多个蓝牙缺陷可使攻击者假冒合法设备
  11. 2.Kong入门与实战 基于Nginx和OpenResty的云原生微服务网关 --- Kong 的安装和基本概念
  12. jspstudy mysql_使用JspStudy集成环境快速部署jsp项目
  13. python实现下载韩寒博客中的所有文章,在本地存储
  14. 【数据库原理】SQL语句练习
  15. MATLAB-Control System Toolbox™0.控制系统工具箱说明
  16. Python实战:将头像变成动漫风
  17. ubuntu 安装teamviewer
  18. 8本好书上新:越忙越要多读书
  19. 遇见OFFER,阿里云最强技术团队现身招聘,“职”为你来
  20. php 算生存曲线,生存曲线(三):统计分析方法这么多,到底选哪个?

热门文章

  1. PHP操作Redis步骤详解
  2. 来聊一聊Cookie(小甜饼),及其涉及到的web安全吧
  3. 算法与数据结构之二分查找
  4. ActiveMQ点对点的发送和接收
  5. DiscuX END - 553 Envolope sender mismatch with header from..
  6. ubuntu 配置 jdk 环境
  7. linux i2c 设备节点读写
  8. Asp.Net Web Api 2 实现多文件打包并下载文件示例源码
  9. 强制修改mysql 中root的密码
  10. 【v2.x OGE-example 第一节】 绘制实体