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的更多相关文章

  1. 【Java】深入理解ThreadLocal

    一.前言 要理解ThreadLocal,首先必须理解线程安全.线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位.当程序以单线程运行的时候,我们不需要考虑线程安全.然而当一个进程中包 ...

  2. 理解ThreadLocal背后的概念

    介绍 我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候. 前提信息 线程可以理解为一个单独的进程,它有自己的调用栈.在java中每一个线程都有一个调用栈或者 ...

  3. 简单理解ThreadLocal原理和适用场景

    https://blog.csdn.net/qq_36632687/article/details/79551828?utm_source=blogkpcl2 参考文章: 正确理解ThreadLoca ...

  4. CSharpGL(55)我是这样理解PBR的

    CSharpGL(55)我是这样理解PBR的 简介 PBR(Physically Based Rendering),基于物理的渲染,据说是目前最先进的实时渲染方法.它比Blinn-Phong方法的真实 ...

  5. 我是如何理解并使用maven的

    前言 一直想写一篇关于Maven的文章,但是不知如何下笔,如果说能使用,会使用Maven的话,一.两个小时足矣,不需要搞懂各种概念.那么给大家来分享下我是如何理解并使用maven的. 什么是Maven ...

  6. vue是一个渐进式的框架,我是这么理解的

    vue是一个渐进式的框架,我是这么理解的 原文地址 时间:2017-10-26 10:37来源:未知 作者:admin 每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主 ...

  7. 我是这样理解EventLoop的

    我是这样理解EventLoop的 一.前言   众所周知,在使用javascript时,经常需要考虑程序中存在异步的情况,如果对异步考虑不周,很容易在开发中出现技术错误和业务错误.作为一名合格的jav ...

  8. 理解ThreadLocal(之二)

    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码 ...

  9. 理解ThreadLocal(之一)

    ThreadLocal是什么 在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编 ...

随机推荐

  1. 微信公众平台消息接口开发-封装weixin.class.php

    原文:微信公众平台消息接口开发-封装weixin.class.php 一.封装weixin.class.php 由于微信公众平台的通信使用的是特定格式的XML数据,每次接受和回复都要去做一大堆的数据处 ...

  2. cocos2d-x 3.0 rapidJson 解析操作应该注意的细节

    Size visibleSize = Director::getInstance()->getVisibleSize(); Point origin = Director::getInstanc ...

  3. BEGINNING SHAREPOINT&#174; 2013 DEVELOPMENT 文件夹

    BEGINNING SHAREPOINT® 2013 DEVELOPMENT 文件夹 第一部分--開始使用SharePoint 2013 第1章节--SharePoint 2013 介绍 逐渐了解Sh ...

  4. 在asp.net webservice中如何使用session

    原文:在asp.net webservice中如何使用session 原文:刘武|在asp.net webservice中如何使用session 在使用asp.net编写webservice时,默认情 ...

  5. php中echo(),print(),print_r()用法

    原文 php中echo(),print(),print_r()用法 从我对echo(),print(),print_r()这个函数的理解是echo可输入字符串变量常量,print与echo差不多,但p ...

  6. Android小应用-----画画板

    public class MainActivity extends Activity { private ImageView iv; float startX = 0; float startY = ...

  7. unity3d 血液

    这里的人物头像和血条是在3d世界生成的,所以有真正的纵深感和遮挡关系,废话不多说,看我是怎么实现的. 第一步.先在UI Root里制作头像和血条. 这个制作步骤基本和我前面一篇文章介绍头像血条的制作步 ...

  8. C#通过系统API判断已经连接互联网

    win32   API函数的做法: 要用的函数:InternetGetConnectedState 函数原形:BOOL   InternetGetConnectedState(LPDWORD   lp ...

  9. leetcode第24题--Reverse Nodes in k-Group

    problem: Given a linked list, reverse the nodes of a linked list k at a time and return its modified ...

  10. .net操作PDF的一些资源(downmoon收集)

    因为业务需要,搜集了一些.net操作pdf的一些资源,特在此分享. 1.如何从 Adobe 可移植文档格式 (PDF) 文件中复制文本和图形 http://support.microsoft.com/ ...