相信很多人知道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. 介绍一个法国的时间戳server

    特别说明: 以下介绍的法国时间戳server已经停止服务了.我曾发Email给相关站点的管理员.对方回复说他也不知道什么时候能恢复服务,有可能就是遥遥无期了.所以以下的内容仅有參考价值.没法实践了. ...

  2. startup alter.log spfile.ora

    SQL> select * from v$version where rownum=1; BANNER --------------------------------------------- ...

  3. ABP入门系列(21)——切换MySQL数据库

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1. 引言 Abp支持MySql已经不是什么新鲜事了,但按照官方文档:Entity Framewo ...

  4. MPSOC之2——ubuntu环境配置及petalinux安装

    MPSOC的linux开发需要使用petalinux,选择Ubuntu操作系统. 1.Ubuntu 1.1. Ubuntu安装 版本16.04.03 vmare版本:12.0 安装时注意选择" ...

  5. How to use GitHub with Android Studio

    转载于:http://www.wfuyu.com/technology/22499.html 目前业界主流的版本管理工具主要是 svn/git.svn是1种集中式的代码管理工具,而git是1种散布式的 ...

  6. Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/context/embedded/ServletRegistrationBean

    异常信息 2017-09-02 18:06:37.223 [main] ERROR o.s.boot.SpringApplication - Application startup failed ja ...

  7. Unable to resolve persistence unit root URL

    异常信息 时间:2017-03-07 11:46:05,516 - 级别:[ WARN] - 消息: [other] The web application [ROOT] appears to hav ...

  8. 55、js对象

    在python中我们学习了面向对象,javascript也是一门面向对象语言,在JavaScript中除了null和undefined以外其他的数据类型都被定义成了对象. JavaScript的对象简 ...

  9. #ifdef #else #endif #if #ifndef 的用法

    预编译就是在对源文件进行处理之前(如在语法扫描和分析之前),先处理预处理部分,精简代码,然后再进行编译. 预处理命令有:#include 文件包含.#define 宏定义.以及要重点讲的#if.#if ...

  10. 使用node的fs读取文件

    啊啊啊啊啊啊啊啊啊啊啊啊啊啊,被node的fs坑了一下午,我又爬上来了,要坚强的笑着活下去,嗯,没毛病老铁. let http = require('http'); let fs = require( ...