浅析ThreadLocal
这是我的第一篇博客,条理不是很清晰,不过还是希望能对大家有所帮助。
首先明确一下这个类的作用,ThreadLocal类是用来为每个线程提供了一份变量的副本,即每个线程的局部变量。每个线程都在自己的栈空间里存有这个变量的值(对象的话就是引用),它们之间是线程隔离的,一个线程对这个ThreadLocal变量做的修改不会被其他线程看见。
下面先看一个例子。
import java.util.Random;
public class ThreadLocalTest {
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 6;
}
};
public static int get(){
return threadId.get();
}
public static void main(String[] args) {
new Thread(new MThread("线程A")).start();
new Thread(new MThread("线程B")).start();
new Thread(new MThread("线程C")).start();
}
static class MThread implements Runnable{
private String name;
public MThread(String str){
name = str;
}
public void run() {
int id = get();
System.out.println("thread "+ name +"'s Id : "+ id);
threadId.set(10 * new Random().nextInt(10));
for(int i = 0; i<3; i++){
id = get();
System.out.println("**thread "+ name +"'s Id : "+ id);
}
}
}
}
结果:
thread 线程B's Id : 6
**thread 线程B's Id : 60
**thread 线程B's Id : 60
**thread 线程B's Id : 60
thread 线程A's Id : 6
**thread 线程A's Id : 50
**thread 线程A's Id : 50
**thread 线程A's Id : 50
thread 线程C's Id : 6
**thread 线程C's Id : 90
**thread 线程C's Id : 90
**thread 线程C's Id : 90
从上面的结果中我们可以看出每个线程一开始调用get()得到的值都是6,而从输出顺序可知,线程A第一次调用get()时线程B已经在28行set()过threadId,但是A得到的还是6,说明B线程的改动对A是不可见的。
然后我们看看源码中的注释:
大意是说每个线程都有一份自己的,独立于别人初始化的变量副本。下面就用源码中的example来看看是不是独立初始化的。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int get(){
return threadId.get();
}
public static void main(String[] args) {
new Thread(new MThread("线程A")).start();
new Thread(new MThread("线程B")).start();
new Thread(new MThread("线程C")).start();
}
static class MThread implements Runnable{
private String name;
public MThread(String str){
name = str;
}
public void run() {
int id ;
for(int i = 0; i<3; i++){
id = get();
System.out.println("thread "+ name +"'s Id : "+ id);
}
}
}
}
结果:
thread 线程A's Id : 1
thread 线程A's Id : 1
thread 线程C's Id : 2
thread 线程C's Id : 2
thread 线程B's Id : 0
thread 线程B's Id : 0
thread 线程B's Id : 0
thread 线程A's Id : 1
thread 线程C's Id : 2
从结果中可以看出每个线程的值都是不同的,这就是因为它们是独立初始化的。因为它们第一次调用get()时如果之前没有set()过值或remove()过,则会调用initialValue()方法来初始化值,而这个方法在第6行中进行了覆盖,每次都是返回nextId的值,而nextId每次取完值都会自增,所以每个线程得到的值都会不同。上面的循环3次是为了说明源码注释中的这句:remains unchanged on subsequent calls,后续调用会返回不变的值(当然前提是期间没有通过set()改变过值。。)。
这从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();
}
首先会得到当前调用get()方法的线程对象,然后通过getMap()方法获取到与这个线程绑定的ThreadLocalMap对象,下面是getMap()的实现
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
很简单,就是获取Thread类的成员变量 ThreadLocal.ThreadLocalMap threadLocals = null; 在Thread类中默认为null,接上面的,当Map为空时,就调用setInitialValue();
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;
}
而在setInitialValue()中,调用了覆盖后的initialValue()取得一个值给value,刚刚是因为Map为null而调的这个方法,所以就会执行 createMap(t, value);而这个方法就是new一个ThreadLocalMap对象赋值给参数t线程,构造参数第一个this是Threadlocal对象,用来在Entry中对应着一个value。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}

可以看到ThreadLocalMap中持有一个Entry的数组,而每一个Entry里就有一个ThreadLocal对应着一个value值,再结合get()的源码就可以知道大体思路就是:threadLocal变量的值的获取是首先找到当前线程对象,然后在取得这个线程对象的成员变量threadLocalMap,然后再通过threadLocalMap中的Entry[]取得这个当前ThreadLocal对应的Entry对象,然后Entry.value就得到了最终的值。至于怎么找到当前Thread对应的Entry对象就是下面的代码:
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);
}
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
在第4行放入的时候就是通过ThreadLocal的成员threadLocalHashCode按位与上Entry数组初始化容量-1获得的下标,所以getEntry()时,第10行先得出下标i,再table[i]就能获取得到Entry了。
最后用自己的话说就是:每个线程都有一个ThreaLocalMap变量,这个Map里面有一个Entry数组,里面保存了很多个ThreadLocal和Value的映射,当你调用get()时,发现线程没有ThreadLocalMap就创建一个ThreadLocalMap,发现Entry数组里没有找到这ThreadLocal对应的Entry就创建一个Entry,在把值(这个值的获取就是在那个initialValue里面)放进去就好了,最后返回这个值。由此可知每个线程都是有用一块空间来保存这个变量的,所以是线程隔离的,互不干扰的。
至于set()方法其实是类似的,看源码就能知道了。
remove()方法提供主动删除不再使用的threadLocal对应的Entry,这样能避免出现内存泄漏。
在getEntry()中也有调用getEntryAfterMiss()来保障能清理掉stale(陈旧)的Entry,能在很大程度上解决内存泄漏问题。

这里说只要线程还存活,都有持有一个隐式的引用到threadLocal变量,这个引用链为:thread->threadLocalMap->Entry->threadLocal,当线程结束后,其所有的线程局部实例的副本都会被GC回收(除非还存在其他的引用到这些副本)。
非常感谢你能看到最后,如果有什么意见或建议,欢迎大家指出,我会积极改进的。
浅析ThreadLocal的更多相关文章
- 浅析 ThreadLocal
一.ThreadLocal类说明 ThreadLocal,很容易让人望文生义,直译"本地线程".ThreadLocal不是一个thread,是thread的局部变量.使用Threa ...
- ThreadLocal 从源码角度简单分析
目录 ThreadLcoal源码浅析 ThreadLocal的垃圾回收 Java引用 ThreadLocal的回收 各线程中threadLocalMap的回收 内存泄露问题 总结 参考 ThreadL ...
- Java并发包2--ThreadLocal的使用及原理浅析
ThreadLocal 是本地线程变量,是一个以ThreadLocal对象为key,任意对象为value的存储结构. 一.使用案例 1.定义线程类MyThread,代码如下: public class ...
- ThreadLocal浅析
1.ThreadLocal的大体理解 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个变量值的副本,并且每一个线程都可以 ...
- 浅析Linux操作系统工作的基础
环境:lubuntu 13.04 kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ...
- spring源码浅析——IOC
=========================================== 原文链接: spring源码浅析--IOC 转载请注明出处! ======================= ...
- ThreadLocal原理分析与代码验证
ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...
- 浅析MyBatis(三):聊一聊MyBatis的实用插件与自定义插件
在前面的文章中,笔者详细介绍了 MyBatis 框架的底层框架与运行流程,并且在理解运行流程的基础上手写了一个自己的 MyBatis 框架.看完前两篇文章后,相信读者对 MyBatis 的偏底层原理和 ...
- 老生常谈系列之Aop--Spring Aop原理浅析
老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...
随机推荐
- 打印man手册为pdf文件
只需要一个命令就可以了! merlin@tfAnalysis:~/projects/tfadc$ man -t errno | ps2pdf - ~/errno.pdf 输出的文件很漂亮.
- leetcode[60] Rotate List
题目:给定链表,和一个k,把链表的后k个旋转到前头,例如链表为: 1->2->3->4->5->NULL and k = 2, return 4->5->1- ...
- unix域套接字UDP网络编程
unix域套接字UDP网络编程,服务器如下面: #include <stdio.h> #include <stdlib.h> #include <string.h> ...
- 用css样式围剿等高列问题(转载)
明修栈道暗度陈仓 该秘籍的心法只有十二个字:”隐藏容器溢出,正负内外边距.”看完下面的几行代码,再看这句话你真的可以看到圣光! 隐藏容器溢出.将外层容器的溢出设为隐藏: .container { ov ...
- Xamarin.Android
Xamarin.Android之使用百度地图起始篇 一.前言 如今跨平台开发层出不穷,而对于.NET而言时下最流行的当然还是Xamarin,不仅仅能够让我们在熟悉的Vs下利用C#开发,在对原生态类库的 ...
- twobin博客样式
twobin博客样式—“蓝白之风” 自暑假以来,囫囵吞枣一般蒙头栽入前端自学中,且不说是否窥探其道,却不自觉中提高了对网页版面设计的要求,乃至挑剔.一个设计清爽美观的网页能让读者心旷神怡,甚至没有 ...
- GoLang获取struct的tag
GoLang获取struct的tag内容:beego的ORM中也通过tag来定义参数的. 获取tag的内容是利用反射包来实现的.示例代码能清楚的看懂! package main import ( &q ...
- JavaScript修改Canvas图片
用JavaScript修改Canvas图片的分辨率(DPI) 应用场景: 仓库每次发货需要打印标签, Canvas根据从数据库读取的产品信息可以生成标签JPG, 但是这个JPG图片的默认分辨率(D ...
- [转]编译 JavaScriptCore For iOS
from: http://phoboslab.org/log/2011/06/javascriptcore-project-files-for-ios github: https://github.c ...
- 批量转换cue文件编码
之前在网上下载的无损(flac.ape),好多都是整盘的,也就是说一个flac或ape文件搭配一个cue分轨文件,这个文件记录着在不同时间段是哪一首歌曲. 由于之前的操作都是在windows下进行的, ...