谈谈对ThreadLocal类的理解
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code 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).
*/
//几个静态常量
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
从上面的官方解释中我们可以得出三点
- 这个类提供线程局部变量
- 这些变量与普通的变量不同,因为每个访问一个变量的线程(通过其{@code get}或{@code set}方法)都有自己的、独立初始化的变量副本。
- {@code ThreadLocal}实例通常是希望将状态与线程关联的类中的私有静态字段。
public void set(T value) {
//获取当前线程Thread
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过阅读源码,可以知道ThreadLocal的set方法主要分为以下几个步骤
- 获取当前线程t
- 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类ThreadLocalMap 对象map,如果map存在,则调用map.set(this, value)直接保存,否则调用createMap(t, value)方法先创建ThreadLocalMap 对象,在创建的过程中进行保存。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal的remove方法比较简单,就是通过当前Thread对象获取到ThreadLocalMap对象,如果不为null的话,调用ThreadLocalMap的remove方法实现。
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();
}
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;
}
protected T initialValue() {
return null;
}
通过阅读源码,可以知道ThreadLocal的get方法主要分为以下几个步骤
- 获取当前线程t
- 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类的静态内部类ThreadLocalMap 对象map,如果map不等于null,map.getEntry(this)通过获取ThreadLocalMap.Entry对象,返回Entry对象的value值;如果map为null,则调用setInitialValue()方法去设置ThreadLocalMap初始值,最后结果返回null。
//默认的table容量
private static final int INITIAL_CAPACITY = 16;
//Entry数组,创建ThreadLocalMap的时候会创建一个默认容量的table数组
private Entry[] table;
//当前Entry的长度
private int size = 0;
//下一个要调整大小的值
private int threshold; // Default to 0
上面的四个常量,记录着ThreadLocalMap的状态变化。
//添加具体实现,该方法为ThreadLocalMap的方法
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
//获取默认的Entry数组
Entry[] tab = table;
int len = tab.length;
//获取索引
int i = key.threadLocalHashCode & (len-1);
//遍历tab,如果存在直接更新
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果不存在,怎创建新的
tab[i] = new Entry(key, value);
int sz = ++size;
//满足条件数组扩容x2
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
该方法是ThreadLocal的set(T value)的具体实现,通过阅读源码可以知道set(ThreadLocal<?> key, Object value)的实现具体分为以下几个步骤
- 获取当前table数组tab、tab当前长度、根据当前线程k的threadLocalHashCode值获取索引i
- 遍历tab如果已经存在Entry对象e,通过e获取当前Threadlocal对象k。
- 如果k和传入的key相等,则将传入的value赋值给e.value直接更新;
- 如果k == null,说明threadLocal强引用已经被释放掉,那么就无法不能通过key去访问到相应的value,说明此时存在脏Entry,可以理解为内存泄漏,则调用replaceStaleEntry(key, value, i)去处理,用当前插入的值替换掉这个key为null的“脏”entry。
- 如果当前tab[i]不存在,则使用new Entry(key, value)创建新的Entry放进tab[i],修改size,插入后调用cleanSomeSlots(int i, int n)再次清除一些key为null的“脏”entry,如果大于阈值就需要进行扩容。
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);
} private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
该方法是ThreadLocal的get()的具体实现,通过阅读源码后可以知道具体实现步骤
- 根据传入的key获取索引
- 根据索引获取Entry e
- 如果e不为null,并且e.get()得到的Threadlocal等于传入的key,则返回e
- 如果e为null,则调用getEntryAfterMiss(key, i, e)方法从tab第i个entry往后遍历,找到对应的key == e.get()则返回e;如果e.get()为null,则调用expungeStaleEntry(i)进行清理;如果e.get()不为null也不等于key,重新计算索引得到e;
- 如果e为null则直接返回null。
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;
}
}
}
该方法是ThreadLocal的remove()的具体实现,通过阅读源码后可以知道具体实现步骤
- 根据传入的key获取索引
- 遍历tab,如果找到对应的key,则清除当前Entry,再调用expungeStaleEntry(i)进行清理
- ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
- Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问;而ThreadLocal采用了“以空间换时间”的方式,每一个线程都提供了一份变量,因此可以同时访问而互不影响。
- 以时间换空间->即枷锁方式,某个区域代码或变量只有一份节省了内存,但是会形成很多线程等待现象,因此浪费了时间而节省了空间。
- 以空间换时间->为每一个线程提供一份变量,多开销一些内存,但是呢线程不用等待,可以一起执行而相互之间没有影响。
- ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
谈谈对ThreadLocal类的理解的更多相关文章
- ThreadLocal类的理解
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各 ...
- Java并发编程之ThreadLocal类
ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...
- 线程系列5--java中的ThreadLocal类实现线程范围内的数据共享(二)
ThreadLocal类可以理解成一个类似与map集合使用,以当前线程当做key 来使用,将线程氛围内需要共享的数据当做value,形成键值对的形式使用.ThreadLocal和线程同步机制都是为了解 ...
- 理解ThreadLocal类
1 ThreadLocal是什么 早在JDK 1.2的版本号中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路. 使用这个工具类能够 ...
- 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类
ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...
- 理解和使用ThreadLocal类
一.从数据结构入手 下图为ThreadLocal的内部结构图 从上面的机构图,可以窥见ThreadLocal的核心机制: 每个Thread线程内部都有一个Map: Map里面存储线程本地对象(key) ...
- ThreadLocal类详解:原理、源码、用法
以下是本文目录: 1.从数据库连接探究 ThreadLocal 2.剖析 ThreadLocal 源码 3. ThreadLocal 应用场景 4. 通过面试题理解 ThreadLocal 1.从数据 ...
- 用ThreadLocal类实现线程安全的正确姿势
大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来.ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量 ...
- 2015年11月26日 Java基础系列(三)ThreadLocal类初级学习
序,ThreadLocal类是为了解决多线程的安全问题.线程安全的意思也就是说每个线程操作自己的变量,不要对其他线程的值造成影响. 在很多情况下,ThreadLocal比直接使用synchronize ...
随机推荐
- Windows 10 MSDN官方原版ISO镜像(简体中文)下载
http://www.heu8.com/2800.html 硬件要求如下:处理器:1 GHz 或更快的处理器或 SoC RAM:1 GB(32 位)或 2 GB(64 位) 硬盘空间:16 GB(32 ...
- 数学--数论--HDU 2136(素数筛选法)
Everybody knows any number can be combined by the prime number. Now, your task is telling me what po ...
- Linux安装Git-两种方式详细教程)
一.Git介绍 Git --- The stupid content tracker, 傻瓜内容跟踪器.Linus Torvalds 是这样给我们介绍 Git 的. Git 是用于 Linux内核开发 ...
- Java——深入理解Java异常体系
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 前言: Java的基 ...
- Java——Java集合那些事
集合概述: 集合和数组都可以保存多个对象,但是数组的长度不可变,集合可以保存数量变化的数据.java中的集合类主要由两个接口派生出,Collection和Map Collection接口和Iterat ...
- 模块(类)之间解耦利器:EventPublishSubscribeUtils 事件发布订阅工具类
如果熟悉C#语言的小伙伴们一般都会知道委托.事件的好处,只需在某个类中提前定义好公开的委托或事件(委托的特殊表现形式)变量,然后在其它类中就可以很随意的订阅该委托或事件,当委托或事件被触发执行时,会自 ...
- B - Lawrence HDU - 2829 斜率dp dp转移方程不好写
B - Lawrence HDU - 2829 这个题目我觉得很难,难在这个dp方程不会写. 看了网上的题解,看了很久才理解这个dp转移方程 dp[i][j] 表示前面1~j 位并且以 j 结尾分成了 ...
- C - A Plug for UNIX POJ - 1087 网络流
You are in charge of setting up the press room for the inaugural meeting of the United Nations Inter ...
- Linux的vi和vim编辑器
Linux中分为:一般模式,插入模式和底行模式 一般模式(通过按iaoIAO键)-->插入模式 插入模式(按Esc键)--> 一般模式 一般模式(通过按:键)-->底行模式 底行模式 ...
- Spring官网阅读(十五)Spring中的格式化(Formatter)
文章目录 Formatter 接口定义 继承树 注解驱动的格式化 AnnotationFormatterFactory FormatterRegistry 接口定义 UML类图 FormattingC ...