1、ThreadLocal是什么

从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

2、ThreadLocal怎么用

public class TestTreadLocal {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread thread = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("kkk");
String s = threadLocal.get();
System.out.println(s);
threadLocal.remove();
} });
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("iiiii");
String s = threadLocal.get();
System.out.println(s);
threadLocal.remove();
}
});
thread.start();
thread1.start();
}
}

上述可以看到,不同的线程对共享的变量操作都互不影响

3、ThreadLocal源码分析

接下来我们看看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);
}

jiexialo从set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。

接下来,看一下threadlocal源码

   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<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//省略

可以看到ThreadloacalMap就是threadloacal的静态内部类,里面定义了一个entry保存数据,以threadloacal为key,我们设置的值value作为value

getmap()源码


//ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

上述代码其实就是调用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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

上述为get()源码,可以看出首先获取当前线程threadlocalmap,如果map不为null,则获取当前线程的entry。entry的值value就作为value返回

那如果map为null则初始化一个值

    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);//创建一个map
return value;
} protected T initialValue() {
return null;//null
}

remove()源码

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

从源码可以看出,将当前线程作为可以移除。源码跟下去看看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;
}
}
}

可以看出,remove会移除该线程的所有value

总结:

(1)每个Thread维护着一个ThreadLocalMap的引用

(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

(6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

OK,现在从源码的角度上不知道你能理解不,对于ThreadLocal来说关键就是内部的ThreadLocalMap。

4、ThreadLocal内存泄漏问题

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

ThreadLocal是什么?谈谈你对他的理解的更多相关文章

  1. 面试官:知道ThreadLocal嘛?谈谈你对它的理解?(基于jdk1.8)

    https://zhuanlan.zhihu.com/p/99150038   ​ 西北工业大学 计算机技术硕士在读 在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的 ...

  2. 谈谈嵌套for循环的理解

    谈谈嵌套for循环的理解     说for的嵌套,先说一下一个for循环的是怎么用的.      这次的目的是为了用for循环输出一个乘法口诀表,一下就是我的一步步理解.    一.   语法:   ...

  3. JVM(一),谈谈你对java的理解

    一.谈谈你对java的理解 1.Java特性 (1)平台无关性 一次编译到处运行 (2)GC 垃圾回收机制 (3)语言特性 泛型-反射机制-lambda表达式 (4)面向对象 面向对象语言-三大特性( ...

  4. 【面试普通人VS高手系列】谈谈你对AQS的理解

    AQS是AbstractQueuedSynchronizer的简称,是并发编程中比较核心的组件. 在很多大厂的面试中,面试官对于并发编程的考核要求相对较高,简单来说,如果你不懂并发编程,那么你很难通过 ...

  5. 【Java面试】面试遇到宽泛的问题,这么回答就稳了,谈谈你对Redis的理解

    "谈谈你对Redis的理解"! 面试的时候遇到这类比较宽泛的问题,是不是很抓狂? 是不是不知道从何开始说起? 没关系,今天我用3分钟教你怎么回答. 大家好,我是Mic,一个工作了1 ...

  6. 谈谈对Spring IOC的理解(转)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  7. 谈谈对Spring IOC的理解

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  8. Spring系列之谈谈对Spring IOC的理解

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IOC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  9. 谈谈对Spring IOC的理解【转】

    学习过Spring框架的人 一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大 ...

随机推荐

  1. python2.1 if-else条件语句:

    #案例:存款大于100万,买宝马:小于20万自行车! cunkuan=10#if判断条件,else:不满足if执行其他命令if cunkuan>100: print("可以买宝马,好开 ...

  2. 041_go语言中的panic

    代码演示: package main import "os" func main() { // panic("a problem") _, err := os. ...

  3. Django REST framework 单元测试

    Django REST framework 单元测试 只是简单记录一下测试代码怎么写 环境 Win10 Python3.7 Django2.2 项目 参照官网 快速开始 写了一个 demo 测试 参照 ...

  4. 01-java实现动态数组

    01-手撸动态数组 本篇是恋上数据结构第一季个人总结 借鉴https://juejin.im/post/6844904001478066183#heading-0 本人git https://gith ...

  5. 类型SQL注入实验 Part1

    准备为PHPstudy环境 <?php $id = $_GET['t']; $conn = mysql_connect("127.0.0.1","root" ...

  6. 用 cgo 生成用于 cgo 的 C 兼容的结构体

    假设(并非完全假设,这里有 demo)你正在编写一个程序包,用于连接 Go 和其它一些提供大量 C 结构体内存的程序.这些结构可能是系统调用的结果,也可能是一个库给你提供的纯粹信息性内容.无论哪种情况 ...

  7. 制作 macOS Sierra U盘USB启动安装盘方法教程 (亲测)

    备注:相关镜像到apple官网下载 https://discussionschinese.apple.com/thread/250596904 进去点击"请使用这个 App Store 链接 ...

  8. 2020-04-20:对Java接口代理模式的实现原理的理解?

    静态代理Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject).静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动 ...

  9. JavaScript 中 Blob对象的初步认识

    Blob Binary Large Object的缩写,二进制大对象 虽然在前端中开发并不常见,但是实际上MySql数据库中,可以通过设置一个Blob类型的数据来存储一个Blob对象的内容 语法 le ...

  10. # c++运算符重载之 前置++, 后置++, 负号运算符, 类型转换函数, 以及输入输出运算符

    c++运算符重载之 前置++, 后置++, 负号运算符, 类型转换函数, 以及输入输出运算符 标签(空格分隔): c++ 前言 我在c++学习的过程中, 对这几个不太常见的运算符重载不太会写.出现了很 ...