Java并发编程(七)-- ThreadLocal
提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和认识,希望让大家理解ThreadLocal更加透彻一些。
ThreadLocal是什么
ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。
用法简介
创建,支持泛型
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。
set方法
mStringThreadLocal.set("jackPotHan");
get方法
mStringThreadLocal.get();
初始化
由于ThreadLocal对象的set()方法设置的值只对当前线程可见,那有什么方法可以为ThreadLocal对象设置的值对所有线程都可见。
为此,我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。如下所示:
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return "This is the initial value";
}
};
此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。
完整的使用示例
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Override
public void run() {
threadLocal.set( (int) (Math.random() * 100D) );
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}
上面创建了两个线程共享一个MyRunnable实例。每个线程执行run()方法的时候,会给同一个ThreadLocal实例设置不同的值。如果调用set()方法的时候用synchronized关键字同步,而且不是一个ThreadLocal对象实例,那么第二个线程将会覆盖第一个线程所设置的值。
然而,由于是ThreadLocal对象,所以两个线程无法看到彼此的值。因此,可以设置或者获取不同的值。
如何实现
为了更好的掌握ThreadLocal,我认为了解其内部实现是很有必要的,这里我们以set方法从起始看一看ThreadLocal的实现原理。
下面是ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先获取当前线程
利用当前线程作为句柄获取一个ThreadLocalMap的对象
如果上述ThreadLocalMap对象不为空,则设置值,否则创建这个ThreadLocalMap对象并设置值
下面是一个利用Thread对象作为句柄获取ThreadLocalMap对象的代码
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//实际上是Thread对象的threadLocals变量
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
//如果一开始设置,即ThreadLocalMap对象未创建,则新建ThreadLocalMap对象,并设置初始值。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从上面可以看出:实际上ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。
对象存放在哪里
在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?
其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。
它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
关于堆和栈的比较,请参考Java中的堆和栈的区别
InheritableThreadLocal
真的只能被一个线程访问么?既然上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?
的确,ThreadLocal的值只能被当前线程访问,其他线程无法对其进行访问、操作,但是Inheritable ThreadLocal可以实现对其他线程甚至是父线程的访问。
InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。
下面我们分析一下InheritableThreadLocal的实现细节,下面展示了InheritableThreadLocal提供的方法:

InheritableThreadLocal继承了ThreadLocal,然后重写了上面三个方法,所以除了上面三个方法之外,其他所有对InheritableThreadLocal的调用都是对ThreadLocal的调用,没有什么特别的。
会导致内存泄露么
有网上讨论说ThreadLocal会导致内存泄露,原因如下
首先ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有。
如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活
所以,ThreadLocal设定的值被持有,导致内存泄露。
上面的逻辑是清晰的,可是ThreadLocal并不会产生内存泄露,因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。
参考资料:
https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
Java并发编程(七)-- ThreadLocal的更多相关文章
- Java并发编程:ThreadLocal
Java并发编程:深入剖析ThreadLocal Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用 ...
- java并发编程学习: ThreadLocal使用及原理
多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点. 先来看一下示例: package yjmyz ...
- Java并发编程--理解ThreadLocal
另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html 本文参考:http://blog.csdn.net ...
- Java并发编程:ThreadLocal的使用以及实现原理解析
前言 前面的文章里,我们学习了有关锁的使用,锁的机制是保证同一时刻只能有一个线程访问临界区的资源,也就是通过控制资源的手段来保证线程安全,这固然是一种有效的手段,但程序的运行效率也因此大大降低.那么, ...
- Java并发编程 (七) J.U.C之AQS
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一. J.U.C之AQS-介绍 1.定义: AbstractQueuedSynchronizer简称AQ ...
- Java并发编程:深入剖析ThreadLocal(转载)
Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...
- (转)Java并发编程:深入剖析ThreadLocal
Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...
- [转载]Java并发编程:深入剖析ThreadLocal
原文地址:http://www.cnblogs.com/dolphin0520/p/3920407.html 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨 ...
- 【转载】 Java并发编程:深入剖析ThreadLocal
原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
随机推荐
- numpy 与 pandas
numpy: import numpy as np np.array([1,2,3]) 创建数组 np.arange(10).reshape(2,5) 类似于range(起始,终止,步长),可以加re ...
- Python变量的作用域
局部变量 局部变量是指在函数内部定义并使用的变量,他只在函数内部有效.即函数内部的名字只在函数运行时才会创建,在函数运行之前或者运行完毕之后,所有的名字就都不存在了.所以,如果在函数外部使用函数内部定 ...
- (转)一位资深程序员大牛给予Java初学者的学习路线建议
Java学习这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是你是如何学习Java的,能不能给点建议?今天我是打算来点干货,因此咱们就不说一些学习方法和技巧了,直接来谈每 ...
- docker日志清理
前言:docker运行久了,会发现它的映射磁盘空间爆满,尤其是yum安装的docker的 解决方法: 1. 用脚本清理,一般yum安装的docker,其存储空间一般都在/var/lib/docker/ ...
- Just oj 2018 C语言程序设计竞赛(高级组)D: 四边形面积
D: 四边形面积 时间限制: 1 s 内存限制: 128 MB 提交 我的状态 题目描述 有一个四边形,现在需要求它的面积 输入 输入四行,每行两个数整数xx, yy (1≤x,y ...
- SVN不要显示问号
让SVN不要显示未进行版本控制的文件(夹)图标的问号: 1.选择TortoiseSVN→SettIngs 2.Overlays→取消勾选Unversioned,点击“应用”,然后重启电脑即可
- Git坑换行符自动转换 [转载]
转自https://www.cnblogs.com/zjoch/p/5400251.html 源起 一直想在 GitHub 上发布项目.参与项目,但 Git 这货比较难学啊.买了一本<Git 权 ...
- js 函数参数为什么是按值传递?
首先说下,在js中有两大类数据类型: 基本类型和引用类型. 其次,基本类型有5种: Undefined,Null,Boolean,Number.String, 引用类型有一种: Object(Func ...
- Introduction to boundary integral equations in BEM
Boundary element method (BEM) is an effective tool compared to finite element method (FEM) for resol ...
- docker运行php网站程序
有一个之前的php网站程序需要迁移到K8S,简单调研了下. 基础镜像 官方提供了诸如php:7.1-apache的基础镜像,但是确认必要的扩展,例如gd,当然官方提供了docker-php-ext-i ...