GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/

ThreadLocal 主要解决的是每个线程绑定自己的值,可以将 ThreadLocal 看成全局存放数据的盒子,盒子中存储每个线程的私有数据。

验证线程变量的隔离性

import static java.lang.System.out;

public class Run {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    static class Work extends Thread {

        @Override
public void run() {
threadLocal.set(0);
for (int i = 1; i <= 5; i++) {
// 获取数据
int sum = threadLocal.get();
out.printf("%s's sum = %s\n", getName(), threadLocal.get());
sum += i;
// 写回数据
threadLocal.set(sum);
}
out.printf("END %s's sum = %d\n\n", getName(), threadLocal.get());
}
} public static void main(String[] args) {
Work work1 = new Work(),
work2 = new Work(); work1.start();
work2.start();
}
}

运行结果:

Thread-0's sum = null
Thread-1's sum = null
Thread-1's sum = 1
Thread-1's sum = 3
Thread-1's sum = 6
Thread-1's sum = 10
END Thread-1's sum = 15 Thread-0's sum = 1
Thread-0's sum = 3
Thread-0's sum = 6
Thread-0's sum = 10
END Thread-0's sum = 15 Process finished with exit code 0

从结果来看,两个线程的计算结果一致,ThreadLocal 中隔离了两个线程的数据。

ThreadLocal 源码解析

ThreadLocalMap 内部类

ThreadLocal 中有一个 ThreadLocalMap 内部类,所以 ThreadLocal 实际上是使用一个哈希表来存储每个线程的数据的。

ThreadLocalMapHashMap 不同,其中 Entry 是一个弱引用,这意味着每次垃圾回收运行时都会将储存的数据回收掉。而且它只使用了数组来存储键值对。

ThreadLocalMap 中的 Entry

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

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.getEntry(this) 看起来很奇怪,在前面有这样一行代码:

ThreadLocalMap map = getMap(t);

这个方法获取当前线程的 ThreadLocalMap 对象,所以,虽然 map.getEntry() 中的 key 总是 ThreadLocal 对象本身,但是每个线程都持有有自己的 ThreadLocalMap 对象。

getMap() 方法

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

看到这个方法,get() 方法中 map.getEntry(this) 的迷雾就解开了。这里可以看到返回的是线程中的 threadLocals 属性。那么这里瞟一眼 Thread 的源码:

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

其实每次 get() 时都是先获取了线程自己的 ThreadLocalMap 对象,然后对这个对象进行操作。

set() 方法

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 为当前线程创建一个 ThreadLocalMap 对象
createMap(t, value);
}

set() 方法也是先获取当前线程自己的 ThreadLocalMap 对象,然后再设置数据。如果获取的哈希表为 null,则创建一个。

createMap() 方法

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

createMap() 方法创建一个 ThreadLocalMap 对象,该对象由线程持有。

总结

  • ThreadLocal 可以隔离线程的变量,每个线程只能从这个对象中获取到属于自己的数据。
  • ThreadLocal 使用哈希表来存储线程的数据,而且这个哈希表是由线程自己持有的,每次获取和设值都会先获取当前线程持有的ThreadLocalMap 对象。
  • ThreadLocalMap 中的 key 总是 ThreadLocal 对象本身。
  • ThreadLocalMap 中的 Entry 是弱引用,每次 GC 运行都会被回收。

Java ThreadLocal 的使用与源码解析的更多相关文章

  1. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

  2. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  3. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  4. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  5. 死磕 java同步系列之Semaphore源码解析

    问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...

  6. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  7. [Java多线程]-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  8. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  9. [Java多线程]-Thread和Runable源码解析

    多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...

随机推荐

  1. iOS面试题整理带答案

    iOS面试题整理带答案       找工作,面试是避免不了的! 而技术开发面试,问一些技术相关的问题是必须的,最新的技术可能人人都趋之若鹜,但有些原理和基础的也希望都有了解. 这里整理了一些iOS相关 ...

  2. ZK Watcher 的原理和实现

    什么是 ZK Watcher 基于 ZK 的应用程序的一个常见需求是需要知道 ZK 集合的状态.为了达到这个目的,一种方法是 ZK 客户端定时轮询 ZK 集合,检查系统状态是否发生了变化.然而,轮询并 ...

  3. javaweb应用程序概述

    1.HTTP(超文本传输协议),它是一种主流的B/S架构中应用的通信协议.具有以下特点: 1.1.无状态:服务不会记录客户端每次提交的请求,服务器一旦响应客户端之后,就会结束本次的通信过程,客户端下一 ...

  4. 是的,是你的BFC - CSS中常用

    是的,是你的BFC - CSS中常用     是的,是你的BFC - CSS中常用 CFC 全称:(Block Formatting Contexts)含义是块级格式化上下文),就是一个块级元素的渲染 ...

  5. 24 (OC)* 加密

    一 .MD5加密 MD5加密是最常用的加密方法之一,是从一段字符串中通过相应特征生成一段32位的数字字母混合码. MD5主要特点是 不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样(也不是 ...

  6. 《Java7并发编程实战手册》读书笔记

    一.线程管理 1.线程的创建和运行 创建线程的2种方式: 继承Thread类,并覆盖run()方法 创建一个实现Runnable接口的类.使用带参数的Thread构造器来创建Thread对象 每个Ja ...

  7. 实现一个正则表达式引擎in Python(二)

    项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...

  8. Python3.7.4入门-3函数

    3 函数 3.1 定义函数 def fib(n): # write Fibonacci series up to n """Print a Fibonacci serie ...

  9. python,json解析字符串时ValueError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

    今天写测试工具的时候,去excel取数据,用json解析字符串为字典时报错,后经调试,发现是单引号的原因,将单引号换位双引号即可 def getExcelValue_to_dic(filepath): ...

  10. 基于djiango实现简易版的图书管理系统

    介绍: 本程序仅仅实现图书数据的增删查 树形结构如下   全部代码如下: url: from django.urls import path from front import views as fr ...