相信很多人知道ThreadLocal是针对每个线程的,但是其中的原理相信大家不是很清楚,那咱们就一块看一下源码。

  首先,我们先看看它的set方法。非常简单,从当前Thread中获取map。那么这个getMap方法是什么样的呢?咱们继续看。

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

  从当前线程中去获取单钱线程的threadLocals. 继续跟进t.threadLocals。

 /**
* 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;
}

这个threadLocals定义在Thread里边,也就是每个线程里边维护者自己的threadLocals,这样使得每个线程之间独立了。思路再次回头上边的set方法上,如果map不为null, 那么重新进行set操作,如果为空,则需要创建这个ThreadLocalMap。咱们看如何进行的创建。

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

以下为创建的过程,在ThreadLocalMap中有一个静态内部类,静态内部类和普通的类是一样的,静态属性和方法和静态内部类不一样,这一点大家要注意。首先根据当前的ThreadLocal的实例获取当前的hashCode然后再与(初始容量-1)结果为7,Entry是ThreadLocalMap的静态内部类,类型为弱引用,也就是说,如果里边的key为null时,该value就是过期的value, 后续将会被擦除。


/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
} /**
* 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;
}
}

  刚才是如果map为空的时候,重新创建了一个ThreadLocalMap赋值给了当前的Thread, 如果当前thread的map不为空时,那么就需要获取当前的threadLocalMap,然后重新将值set上。

  这块是它的set方法,那么咱们看一个下get方法,获取当前的thread,并且从thread中获取threadLocalMap,然后取出其中的value,如果没有找到这个map的话,初始化一个key为ThreadLocal的实例放入到当前的thread中的ThreadLocalMap中,并且将value设置为null。最后将null返回给get方法。

 /**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}

  这块内容大致的方法基本上就是这么多,那么我们在什么场景下使用呢?

  一般在web应用中context可以使用。比如SimpleDateFormat是非线程安全的,那么我们就可以借助这个类去进行处理,为了显示不同的格式。我自己写了一个例子如下:

package com.hqs;

import java.text.SimpleDateFormat;
import java.util.Date; /**
* Created by huangqingshi on 2018/1/14.
*/
public class ThreadLocalExample {
//SimpleDateFormat 不是线程安全的
private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat();
}
}; public static void main(String[] args) {
SimpleDateFormat sdf = threadLocal.get();
sdf.applyPattern("yy/MM/dd");
System.out.println(sdf.format(new Date()));
sdf.applyPattern("MM/dd/yyyy");
System.out.println(sdf.format(new Date()));
} }

18/01/14
01/14/2018


Process finished with exit code 0

  使用这个ThreadLocal的时候,需要注意一些内容:

  如果定义一些大量的线程变量的,可能会出现不能创建线程的异常,因为一些web server针对线程会定义线程池,当使用ThreadLocal创建了很多线程,处理的时间比较久,那么这些线程不会释放,结果会导致栈溢出,程序没有响应。此时需要注意使用的时候,尽可能让当前线程处理完成。

  还有可能会因此出现Perm溢出,比如ThreadLocal里边装入了一些静态的类,那么静态的类初始化的时候会在永久代,而且不会释放,那么创建对象的数量增多的话会导致线程出现这总异常,那么我们在这种情况下需要进行remove操作。如果ThreadLocal里边放入了一些基本类型话不会出现这种情况。

  还有一种情况就是如果线程中的对象需要进行数据共享的话需要将ThreadLocal设置为静态变量。

  好了,针对这块内容的分析和整理就这么多了,如果大家有什么问题的话,请告知~  谢谢。

聊聊ThreadLocal原理以及使用场景-JAVA 8源码的更多相关文章

  1. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  2. Hadoop之HDFS原理及文件上传下载源码分析(下)

    上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ...

  3. 程序兵法:Java String 源码的排序算法(一)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...

  4. Java读源码之ReentrantLock(2)

    前言 本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑. Java读源 ...

  5. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  6. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  7. java io 源码研究记录(一)

    Java IO 源码研究: 一.输入流 1  基类 InputStream 简介: 这是Java中所有输入流的基类,它是一个抽象类,下面我们简单来了解一下它的基本方法和抽象方法. 基本方法: publ ...

  8. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  9. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

随机推荐

  1. Codeforces Round #261 (Div. 2)459D. Pashmak and Parmida&#39;s problem(求逆序数对)

    题目链接:http://codeforces.com/contest/459/problem/D D. Pashmak and Parmida's problem time limit per tes ...

  2. Lucene41PostingWriter源代码分析

    原来看lucene4.0的posting格式(http://blog.csdn.net/jollyjumper/article/details/30017581),发现这还是比較简单的VInt格式,据 ...

  3. Android Api 检查參数状态Api

    转载请注明出处:http://blog.csdn.net/droyon/article/details/39938677 在进行Android应用程序开发中,android提供了一个非常好的工具类,来 ...

  4. 在Visual Studio Code中开发Office Add-in

    作者:陈希章 发表于 2017年7月13日 上一篇 我介绍了如何在Visual Studio中开发Office Add-in,因为有标准的项目模板,一系列配套的工具,尤其是自带的一键调试功能,可以让开 ...

  5. Asp.net MVC 填充word并下载

    使用Aspose.word填充内容并下载(免费版Aspose) 填充固定模式的Word文档,需要先制作Word模板: Aspose.Words主要通过Words里域(Fields)来控制内容.     ...

  6. SSM学习(一)搭建基础框架

    不知不自觉,工作也两年多了,由于公司一直用的是ssh框架,所以所学也一直是ssh.直到有一天,服务器被攻击,tomcat目录下总有莫名其妙的一些文件,这些文件通过远程ftp下载了一些病毒和木马,服务器 ...

  7. SQL 杂活

    例子一:查询两个表数据并且分页展示 select * from ( select ROW_NUMBER() OVER(order by CreateTime desc) as rownum,* fro ...

  8. 接口json数据与数据库数据循环比对校验

    创建测试计划,加载数据库驱动: 线程组: csv配置元件: 注:Filename用的是相对路径,csv文件要与jmeter脚本文件在同一目录 JDBC连接配置: jdbc请求: 用户定义的变量: ht ...

  9. 【JMeter】获取json响应报文中数组长度

    import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; import net.minidev.json.J ...

  10. cell上添加倒计时,以及时差问题的解决

    原址 http://www.cnblogs.com/zhangmaliang/p/5102518.html 最近项目中用到了tableView的多个cell倒计时系统问题,本觉得很简单的一个事,一做发 ...