转载自:掘金大闲人柴毛毛博客。

ThreadLocal是开发中最常用的技术之一,也是面试重要的考点。本文将由浅入深,介绍ThreadLocal的使用方式、实现原理、内存泄漏问题以及使用场景。

ThreadLocal作用

在并发编程中时常有这样一种需求:每条线程都需要存取一个同名变量,但每条线程中该变量的值均不相同。

如果是你,该如何实现上述功能?常规的思路如下: 使用一个线程共享的Map<Thread,Object>,Map中的key为线程对象,value即为需要存储的值。那么,我们只需要通过map.get(Thread.currentThread())即可获取本线程中该变量的值。

这种方式确实可以实现我们的需求,但它有何缺点呢?——答案就是:需要同步,效率低!

由于这个map对象需要被所有线程共享,因此需要加锁来保证线程安全性。当然我们可以使用java.util.concurrent.*包下的ConcurrentHashMap提高并发效率,但这种方法只能降低锁的粒度,不能从根本上避免同步锁。而JDK提供的ThreadLocal就能很好地解决这一问题。下面来看看ThreadLocal是如何高效地实现这一需求的。

如何使用ThreadLocal

在介绍ThreadLocal原理之前,首先简单介绍一下它的使用方法。

public class Main{
private ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public void start() {
for (int i=0; i<10; i++) {
new Thread(new Runnable(){
@override
public void run(){
threadLocal.set(i);
threadLocal.get();
threadLocal.remove();
}
}).start();
}
}
}
  • 首先我们需要创建一个线程共享的ThreadLocal对象,该对象用于存储Integer类型的值;
  • 然后在每条线程中可以通过如下方法操作ThreadLocal:
    • set(obj):向当前线程中存储数据
    • get():获取当前线程中的数据
    • remove():删除当前线程中的数据

ThreadLocal的使用方法非常简单,关键在于它背后的实现原理。回到上面的问题:ThreadLocal究竟是如何避免同步锁,从而保证读写的高效?

ThreadLocal实现原理

ThreadLocal的内部结构如下图所示:

ThreadLocal并不维护ThreadLocalMap,并不是一个存储数据的容器,它只是相当于一个工具包,提供了操作该容器的方法,如get、set、remove等。而ThreadLocal内部类ThreadLocalMap才是存储数据的容器,并且该容器由Thread维护。

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。

ThreadLocalMap由一个个Entry对象构成,Entry的代码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

Entry继承自WeakReference<ThreadLocal<?>>,一个EntryThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

那么,ThreadLocal是如何工作的呢?下面来看set和get方法。

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

为何要使用弱引用?

对弱引用不了解的同学可以参考笔者的另一篇文章:http://blog.csdn.net/u010425776/article/details/50760053。

Java设计之初的一大宗旨就是——弱化指针。 Java设计者希望通过合理的设计简化编程,让程序员无需处理复杂的指针操作。然而指针是客观存在的,在目前的Java开发中也不可避免涉及到“指针操作”。如:

Object a = new Object();

上述代码创建了一个强引用a,只要强引用存在,垃圾收集器是不会回收该对象的。如果该对象非常庞大,那么为了节约内存空间,在该对象使用完成后,我们需要手动拆除该强引用,如下面代码所示:

a = null;

此时,指向该对象的强引用消除了,垃圾收集器便可以回收该对象。但在这个过程中,仍然需要程序员处理指针。为了弱化指针这一概念,弱引用便出现了,如下代码创建了一个Person类型的弱引用:

WeakReference<Person> wr = new WeakReference<Person>(new Person()); 

此时程序员不用再关注指针,只要没有强引用指向Person对象,垃圾收集器每次运行都会自动将该对象释放

那么,ThreadLocalMap中的key使用弱引用的原因也是如此。当一条线程中的ThreadLocal对象使用完毕,没有强引用指向它的时候,垃圾收集器就会自动回收这个Key,从而达到节约内存的目的

那么,问题又来了——这会导致内存泄漏问题!

ThreadLocal的内存泄漏问题

ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时value和value指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!

不过不用担心,ThreadLocal提供了这个问题的解决方案。

每次操作set、get、remove操作时,ThreadLocal都会将key为null的Entry删除,从而避免内存泄漏

那么问题又来了,如果一个线程运行周期较长,而且将一个大对象放入LocalThreadMap后便不再调用set、get、remove方法,此时该仍然可能会导致内存泄漏。

这个问题确实存在,没办法通过ThreadLocal解决,而是需要程序员在完成ThreadLocal的使用后要养成手动调用remove的习惯,从而避免内存泄漏。

ThreadLocal的使用场景

Web系统Session的存储就是ThreadLocal一个典型的应用场景。

Web容器采用线程隔离的多线程模型,也就是每一个请求都会对应一条线程,线程之间相互隔离,没有共享数据。这样能够简化编程模型,程序员可以用单线程的思维开发这种多线程应用。

当请求到来时,可以将当前Session信息存储在ThreadLocal中,在请求处理过程中可以随时使用Session信息,每个请求之间的Session信息互不影响。当请求处理完成后通过remove方法将当前Session信息清除即可。

作者:大闲人柴毛毛
链接:https://juejin.im/post/5aa74967f265da23a334e373
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

揭秘ThreadLocal(转)的更多相关文章

  1. 深度揭秘Netty中的FastThreadLocal为什么比ThreadLocal效率更高?

    阅读这篇文章之前,建议先阅读和这篇文章关联的内容. 1. 详细剖析分布式微服务架构下网络通信的底层实现原理(图解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...

  2. Spark Streaming揭秘 Day7 再探Job Scheduler

    Spark Streaming揭秘 Day7 再探Job Scheduler 今天,我们对Job Scheduler再进一步深入一下,对一些更加细节的源码进行分析. Job Scheduler启动 在 ...

  3. Spark Streaming揭秘 Day5 初步贯通源码

    Spark Streaming揭秘 Day5 初步贯通源码 引子 今天,让我们从Spark Streaming最重要的三个环节出发,让我们通过走读,逐步贯通源码,还记得Day1提到的三个谜团么,让我们 ...

  4. 基础篇:JAVA引用类型和ThreadLocal

    前言 平时并发编程,除了维护修改共享变量的场景,有时我们也需要为每一个线程设置一个私有的变量,进行线程隔离,java提供的ThreadLocal可以帮助我们实现,而讲到ThreadLocal则不得不讲 ...

  5. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  6. Syscan360会议胸牌破解揭秘

    Syscan360会议胸牌破解揭秘 背景 有幸参加今年11月份的上海Syscan360安全会议,会议期间有一个亮点就是360的独角兽团队设计了一款电子badge(胸牌)供参加人员进行破解尝试,类似于美 ...

  7. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  8. Threadlocal使用Case

    Threadlocal能够为每个线程分配一份单独的副本,使的线程与线程之间能够独立的访问各自副本.Threadlocal 内部维护一个Map,key为线程的名字,value为对应操作的副本. /** ...

  9. CI Weekly #9 | 揭秘阿里 Docker 化实践之路

    2017年悄然而至,对 flow.ci 你有什么新的期待呢?新的一年,flow.ci会越来越强大好用,希望继续得到你的支持与反馈.最近,我们做了如下的「功能优化」与「问题修复」,看看有没有你想要的: ...

随机推荐

  1. Ubuntu 12.04下Hadoop 2.2.0 集群搭建(原创)

    现在大家可以跟我一起来实现Ubuntu 12.04下Hadoop 2.2.0 集群搭建,在这里我使用了两台服务器,一台作为master即namenode主机,另一台作为slave即datanode主机 ...

  2. asp.net mvc中DropDownList

    asp.net mvc中DropDownList的使用. 下拉列表框 以分为两个部分组成:下拉列表和默认选项 DropDownList扩展方法的各个重载版本基本上都会传递到这个方法上:   publi ...

  3. 如何理解Hibernate中的HibernateSessionFactory类

    package com.zz.util; import org.hibernate.HibernateException; import org.hibernate.Session; import o ...

  4. How to create functions that can accept variable number of parameters such as Format

    http://www.chami.com/tips/delphi/112696D.html Sometimes it's necessary to pass undefined number of [ ...

  5. mysql select语句执行顺序

        SELECT语句定义       一个完成的SELECT语句包含可选的几个子句. SELECT语句的定义如下: <SELECT clause> [<FROM clause&g ...

  6. Photoshop的版本体系

    Photoshop CS6 和Photoshop CC的区别 没有什么玄乎的,仅仅是版本不一样. 版本的命名 Photoshop CS6 是CS系列的最后一版本, Adobe Photoshop CS ...

  7. Linux下查找命令(收集整理)

    原文:http://blog.csdn.net/sunstars2009918/article/details/8510878 一.Linux查找文件的相关命令 常 用 命 令 简要中文说明 程序所在 ...

  8. PHP:面向对象学习笔记,重点模拟Mixin(掺入)

    背景 相对于Python.Node和Ruby来说PHP算是一门容易学习和使用的语言,因为这个特点也使其成为WEB开发领域的佼佼者,本文记录一下我对PHP面向对象部分的学习笔记. 先来一个复杂的例子:M ...

  9. Sound.loadCompressedDataFromByteArray

    前不久Adobe发布了Flash Player 11的release版本, Flash Player 11 带来了很多新特性, 最最红火的就是Stage3D了,但是这里讲的是这个版本的另一个新特性, ...

  10. 深度学习Deep learning

    In the last chapter we learned that deep neural networks are often much harder to train than shallow ...