我是如何理解ThreadLocal
ThreadLocal的概念
ThreadLocal从英文的角度看,可以看成thread和local的组合,就是线程本地的意思,我们都知道,看过jvm内存分配的人都知道在jvm虚拟机中对每一个线程都分配了一个独立的空间。独立的空间就意味着线程之间是相互隔离的。那么如果在独立空间里面声明某一些东西(也就是线程内部的东西),这个就可以变向的解决多线程程序并发问题了(个人理解)。这个东西就是ThreadLocal,所以说ThreadLocal可以为解决多线程的并发问题提供一种新的思路。稍后threadLocal使用一节会讲解。学过java的人都知道ThreadLocal变量是为使用它的线程提供一个单独的线程局部变量值的副本。所以每个线程都可以独立的改变自己的副本而不会影响其他线程所对应的副本。
说明:在ThreadLocal源码的时候,有一个注释我在这里引用下,如下图。翻译过来就是:该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
从上面的话中我们可以得出以下几点信息
(1) ThreadLocal是一个线程局部的变量。
(2) ThreadLocal 独立于变量的初始化副本,在看ThreadLocal源码的时候会有一个initialValue(),也就是说ThreadLocal可以初始化一个值,然后某个线程可以获取到这个初始化值的副本。
(3) 状态和某一个线程关联,因为ThreadLocal不是是用于共享变量所设计的,而且为了方便线程处理自己的状态而引入的。
Thread关于ThreadLocal讲解
java线程知识太多了,本文只是谈谈ThreadLocal理解,在Thread.java代码中,我们可以看到声明了两个关于ThreadLocal.ThreaLocalMap的变量,ThreadLocalMap主要是用来存放local变量的,以后每个Thread要访问local对象的时候,那么就会使用这两个声明的变量。很多人会问,为什么Thread里面会引用两个ThreadLocalMap对象呢,这两个对象又有什么区别呢?这个问题留给自己去想想,我就不在这里说了,看看字面意思应该就明白了。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; /*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocal源码解析:
(1) 构造函数
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
(2) initialValue 初始化值
这个是一个初始化值的函数,是保护类型的,很明显,作者的意图是想要重载此函数。基本情况下,此函数只会调用一次。在调用第一次调用get方法的时候会调用这个方法。
protected T initialValue() {
return null;
}
具体用户如下:
private static final ThreadLocal<DateFormat> dataFormat = new ThreadLocal<DateFormat>(){
protected DateFormat initialValue() {
// 初始化值
return new SimpleDateFormat("yyyy-HH-MM");
}
};
(2) set方法
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
调用set方法的时候会首先调用getMap来获取一个ThreadLocalMap,还记得在前面说的Thread类里面声明了两个变量吗?这里很巧妙的是,ThreadLocalMap就是获取当前线程里面的声明的ThreadLocalMap,所以说ThreadLocal是线程的局部变量。如果
map为空,当前会创建一个map。
(3)get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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);
return value;
}
get方法没有什么讲的,就是设置而已。
(4) remove方法
/**
* Remove the entry for key.
*/
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;
}
} private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length; // expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
remove它提供了移除此线程局部变量在当前进程的值,在ThreadLocalMap的remove方法会调用expungeStaleEntry的方法,这个方法作用很大,它会把ThreadLocalMap里面保存的key为空的值置空。方便GC处理。所以说在很多人都说在ThreadLocal保存的值在不使用的情况下,最好调用remove方法。
ThreadLocal可能引起的内存泄露
ThreadLocal其实里面存放线程局部变量的是ThreadLocalMap,ThreadLocalMap是一个什么东西呢?字面意思就可以看出是一个map。它主要是用来存放local信息的,我们存放的时候就是以ThreadLocal为key来进行存放的。从源码中可以看出,key是一个弱引用类型。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。下面我们来证明下,请看下面代码。
package com.zh.test;
import java.lang.ref.WeakReference;
import com.zh.test.MyThreadLocal.ThreadLocalMap;
public class Test1 {
public static void main(String[] args) {
MyThreadLocal myThreadLocal = new MyThreadLocal();
myThreadLocal.set(new Object());
ThreadLocalMap threadLocalMap = myThreadLocal.getThreadLocalMap();
threadLocalMap.printAll();
myThreadLocal = null;
System.gc();
threadLocalMap.printAll();
}
}
class MyThreadLocal{
static ThreadLocalMap threadLocalMap = new ThreadLocalMap();
public void set(Object vData){
threadLocalMap.set(this, vData);
}
public ThreadLocalMap getThreadLocalMap(){
return threadLocalMap;
}
static class ThreadLocalMap {
private Entry[] table = new Entry[1];
static class Entry extends WeakReference<MyThreadLocal> {
Object value;
Entry(MyThreadLocal k, Object v) {
super(k);
value = v;
}
}
private void set(MyThreadLocal key, Object value) {
table[0] = new Entry(key, value);
}
public void printAll(){
for (Entry entry : table) {
System.out.println(entry.get());
System.out.println(entry.value);
}
}
}
}
运行上面代码,会发现,在运行GC以前没有任何变化,在运行GC以后,发现key为空了,但是value值却还存在,这说明直接将threadLocal置空,这个方法是不正确的,这个会引起内存泄露的。所以说千万不要把ThreadLocal置空,那么应该如何处理呢,在前面已经说了,在不用了ThreadLocal的时候直接调用remove方法。这样的话内部会把key为空的value值也清楚了。

ThreadLocal的使用
ThreadLocal如何使用呢,在ThreadLocal的源码注释中,就交你如何使用了,本文就不交代了,有时间看看源码,会有意想不到的收获。
ps:可以把一些线程不安全的东西放在ThreadLocal里面比如说dateFormat,数据库连接。。。。。。。
我是如何理解ThreadLocal的更多相关文章
- 【Java】深入理解ThreadLocal
一.前言 要理解ThreadLocal,首先必须理解线程安全.线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位.当程序以单线程运行的时候,我们不需要考虑线程安全.然而当一个进程中包 ...
- 理解ThreadLocal背后的概念
介绍 我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候. 前提信息 线程可以理解为一个单独的进程,它有自己的调用栈.在java中每一个线程都有一个调用栈或者 ...
- 简单理解ThreadLocal原理和适用场景
https://blog.csdn.net/qq_36632687/article/details/79551828?utm_source=blogkpcl2 参考文章: 正确理解ThreadLoca ...
- CSharpGL(55)我是这样理解PBR的
CSharpGL(55)我是这样理解PBR的 简介 PBR(Physically Based Rendering),基于物理的渲染,据说是目前最先进的实时渲染方法.它比Blinn-Phong方法的真实 ...
- 我是如何理解并使用maven的
前言 一直想写一篇关于Maven的文章,但是不知如何下笔,如果说能使用,会使用Maven的话,一.两个小时足矣,不需要搞懂各种概念.那么给大家来分享下我是如何理解并使用maven的. 什么是Maven ...
- vue是一个渐进式的框架,我是这么理解的
vue是一个渐进式的框架,我是这么理解的 原文地址 时间:2017-10-26 10:37来源:未知 作者:admin 每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主 ...
- 我是这样理解EventLoop的
我是这样理解EventLoop的 一.前言 众所周知,在使用javascript时,经常需要考虑程序中存在异步的情况,如果对异步考虑不周,很容易在开发中出现技术错误和业务错误.作为一名合格的jav ...
- 理解ThreadLocal(之二)
想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...
- 理解ThreadLocal(之一)
ThreadLocal是什么 在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编 ...
随机推荐
- 从用python做zoj1011发生Non-zero Exit Code错误说起
近期做了浙大oj的第1011道题,遇见一件奇怪的事.这道题我用c++和php做,提交后都正确.可是用全然同样的逻辑改写成python代码提交后却产生了Non-zero Exit Code的判题结果.p ...
- 编译预处理 -- 带参数的宏定义--【sky原创】
原文:编译预处理 -- 带参数的宏定义--[sky原创] 如有转载请注明出处 编译预处理 -- 带参数的宏定义 前面为输出文件,后面为输入文件 gcc -E -o test.i test.c ...
- svg的自述
svg可缩放矢量图形(Scalable Vector Graphics). SVG 使用 XML 格式定义图像. SVG 是使用 XML 来描述二维图形和绘图程序的语言. 什么是SVG? SVG 指可 ...
- Linq to Sql : 动态构造Expression进行动态查询
原文:Linq to Sql : 动态构造Expression进行动态查询 前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?Expression<Fu ...
- Windows 7的 磁盘管理中,某个磁盘或分区,突然变成只读。
1.今天突然发现E盘无法创建文件夹.文件,也不可以改,感觉像是变成只读 . 2.我的电脑 -> 计算机管理 -> 存储 -> 磁盘管理,发现E盘下面标记着只读两个字. 3.由于我的E ...
- Cts分析框架(4)-添加任务
Debug watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXRmb290YmFsbA==/font/5a6L5L2T/fontsize/400/fill/ ...
- Linux分配给该用户没有权限登陆
Linux分配给该用户没有权限登陆 sudo visudo username ALL=(ALL) NOPASSWD:ALL 版权声明:本文博客原创文章,博客,未经同意,不得转载.
- [Node.js框架] 为什么要开发 Codekart 框架
两年前,在被php的$符号和字符串处理折磨得半夜骂娘之后,我义无反顾地决定:珍爱生命,远离php. 之后一直在寻找一门“完美的语言”,先后接触了Lisp.python.java.Ruby.Lisp几乎 ...
- Mybatis之动态构建SQL语句
今天一个新同事问我,我知道如何利用XML的方式来构建动态SQL,可是Mybatis是否能够利用注解完成动态SQL的构建呢?!!答案是肯定的,MyBatis 提供了注解,@InsertProvider, ...
- css3学习文档
什么是CSS3? CSS3是CSS2的升级版本,3只是版本号,它在CSS2.1的基础上增加了很多强大的新功能. 目前主流浏览器chrome.safari.firefox.opera.甚至360都已经支 ...