ThreadLocal分析
我们再介绍一个在多线程环境中经常使用的类ThreadLocal,它是java为解决多线程程序的并发问题提供了一种新的方向,使用这个ThreadLocal类可以帮助开发者很简单地编写出简洁的程序,并且是线程安全的。ThreadLocal很容易让人误解,认为是一个“本地线程”,其实ThreadLocal并不是一个Thread,而是Thread的一个局部变量。
当使用ThreadLocal变量时,ThreadLocal为每个使用这个变量的线程提供不同的变量副本,所以每一个线程改变的只是自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量。我们先来了解一下ThreadLocal的接口方法
ThreadLocal类提供的接口方法比较简单,很容易使用:
public void set(Object value) 该方法设置当前线程局部变量的值。
public Object get() 该方法返回当前线程所对应的线程局部变量的值。
public void remove() 该方法删除当前线程局部变量的值,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。这这里需要说明的是,当线程结束后,该线程的所有局部变量将自动被垃圾回收器(GC)回收,所以显式调用该方法清除线程的某些局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null。
下面举一个列子来说明一下他的用法。
public class TestThreadLocal {
static class myRunnable implements Runnable {
int index;
public myRunnable(int i){
index =i;
}
public void run() {
myThreadLocal.set("Thread:"+ index);
System.out.println(myThreadLocal.get());
}
}
public static ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
public static void main(String[] args){
Thread thread1 = new Thread(new myRunnable(1));
Thread thread2 = new Thread(new myRunnable(2));
thread1.start();
thread2.start();
System.out.println(myThreadLocal.get());
}
}
执行的结果如下:
Thread:1
Thread:2
null
thread 1 返回Thread:1的结果,thread2返回Thread:2,最后一个null是main线程的执行结果,因为 main线程没有设置自己的局部变量。
ThreadLocal的实现原理
在介绍ThreadLocal变量之前,我们先来看一个Thread的内部变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
这个变量就是线程用来存储自己的ThreadLocal变量的空间,他的类型 ThreadLocal.ThreadLocalMap,作用相当于一个hashmap,对这种数据类型的操作是不需要加锁的,因为只有一个线程(也就是线程自己)才可能操作到自己的变量。
我们先看ThreadLocal源代码是来解析两个比较重要的函数:set和get
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//拿到本地线程map
if (map != null)
map.set(this, value);//map存在时,添加value
else
createMap(t, value);//map不存在时,创建map并且添加value
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //拿到本地线程map
if (map != null) {//map存在时,或者key
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从代码上看,ThreadLocal函数都是先获得当前线程的引用,然后得到线程局部变量,最后以ThreadLocal作为key,设置或者获取value,线程本地变量是类型为ThreadLocalMap的变量,ThreadLocalMap相当于一种哈希表,有兴趣的可以看一下。整个过程没有任何同步操作,因此性能很高。
ThreadLocal与其它同步机制的比较
ThreadLocal和线程同步机制都是为了解决多线程访问变量冲突的问题,同步机制是通过对象的锁机制保证同一时间只有一个线程访问变量,此时该变量是多个线程共享的,使用同步机制要求用户分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等复杂的问题,程序设计和编写难度相对比较大。而ThreadLocal则从另一个角度来解决多线程的并发访问,它会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,因为每一个线程都拥有自己的变量副本,它只操作自己的变量副本,从而也就没有必要对该变量进行同步了。
当然ThreadLocal并不能替代同步机制,两者面向的问题不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。
InheritableThreadLocal
通常线程还需要维护另外一个变量 inheritableThreadLocals
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null
当创建一个新的线程时,线程会从父线程哪里复制一份数据。也就是是说,子线程会享有父线程的数据,下面的代码摘自Thread.init函数,子线程创建时,将会调用。
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
请看它的getMap函数,显然操作的是inheritableThreadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
这时所有的操作都是针对Thread的inheritableThreadLocals,这种操作也是线程安全的。
我们来举一个例子来说明InheritableThreadLocal的说法
public class TestInheritableThreadLocal {
static class myRunnable implements Runnable {
int index;
public myRunnable(int i){
index =i;
}
public void run() {
//
System.out.println("thread " + index + "-" + myThreadLocal.get());
myThreadLocal.set("Thread:"+ index);
System.out.println("thread " + index + "-" + myThreadLocal.get());
}
}
public static InheritableThreadLocal<String> myThreadLocal = new InheritableThreadLocal<String>();
public static void main(String[] args){
myThreadLocal.set("parent main thread");
Thread thread1 = new Thread(new myRunnable(1));
Thread thread2 = new Thread(new myRunnable(2));
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main thead -" + myThreadLocal.get());
}
}
结果如下:
thread 1-parent main thread
thread 2-parent main thread
thread 2-Thread:2
thread 1-Thread:1
main thead -parent main thread
线程1和线程2会共享main线程的数据,子线程也可以修改自己的副本,修改后,线程1得到的结果Thread:1,线程2得到结果Thread:2
而父线程(main)的变量时不会噶边的,因为子线程的修改只作用于自己的变量空间,不会作用到父线程。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而不受影响。
ThreadLocal分析的更多相关文章
- ThreadLocal分析总结:
1.ThreadLocal 是什么 它是一个数据结构,像 HashMap,可保存 "key : value" 键值对:ThreadLocal 有一个内部类ThreadLocalMa ...
- JDK之ThreadLocal分析
ThreadLocal是在是Thread的一个局部变量,今天我来分析了一下这个类 先看ThreadLocal的set方法 public void set(T value) { Thread t = T ...
- Netty源码分析-- ThreadLocal分析(九)
为了更好地探讨Netty的内存模型,后面会用到,这里我还是决定跟大家一起看下ThreadLocal和FastThreadLocal的源码,有的时候我们在看源码的时候会一层层的遇到很多之前没有看过的内容 ...
- ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因
引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...
- 并发基础(十) 线程局部副本ThreadLocal之正解
本文将介绍ThreadLocal的用法,并且指出大部分人对ThreadLocal 的误区. 先来看一下ThreadLocal的API: 1.构造方法摘要 ThreadLocal(): 创建一个线程 ...
- Java 多线程--ThreadLocal Timer ExecutorService
ThreadLocal /** * ThreadLocal:每个线程自身的存储本地.局部区域 * @author xzlf * */ public class ThreadLocalTest01 { ...
- 面经手册 · 第12篇《面试官,ThreadLocal 你要这么问,我就挂了!》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 说到底,你真的会造火箭吗? 常说面试造火箭,入职拧螺丝.但你真的有造火箭的本事吗,大 ...
- 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...
- ThreadLocal 工作原理、部分源码分析
1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...
随机推荐
- react 执行 yarn build 后 去除 .js.map 文件
map文件是帮助我们查看报错的位置的. map文件由devtool属性控制,如果不想要map,注释掉就可以,大约webpack.config.prod.js第57行: // devtool: shou ...
- 最小公倍数 【杭电-HDOJ-1108】 附题+具体解释
/* 最小公倍数 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total S ...
- 区别原生chrome 和以chrome为内核的360浏览器
function isChrome360() { if( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ) { var des ...
- ASP.NET前后台交互之JSON数据
最近由于项目需要做一个Ajax的搜集单表单的多重数据的需求,所以就采用了前端JQuery保存Object对象之后转换成JSON的数据源传递给后台处理的这样的形式,相信有不少人大多时候是接收后台给出的J ...
- web前端规范
无论是从技术角度还是开发视角,对于web前端开发规范文档都有一定规范,本文就css3和html5的发展前景总结了一系列的web开发文档,仅供大家参考. 规范目的:为提高团队协作效率, 便于后台人员添加 ...
- Asp.net2.0里的SessionPageStatePersister
备注: ASP.NET 页可在处理和提供任何网页所必需的原本无状态 HTTP 请求与响应之间存储 Page 状态信息.此状态称为“视图状态”. ASP.NET 的默认持久性机制是使用 HiddenFi ...
- mysql-shell的安装和使用
mysql-shell是一个高级的mysql命令行工具.它直接两种模式(交互式&批处理式)三种语言(javascript\python\sql) 1.下载地址 https://dev.mysq ...
- unity, TRANSFORM_TEX
TRANSFORM_TEX在UnityCG.cginc中定义. ----补充: 为啥buildin shader Unlit-Normal.shader中有一个float4 _MainTex_ST变 ...
- USB3.0测试和使用说明
概述 AC6102上集成了一颗Cypress 推出的高性能USB3.0传输芯片CYUSB3014,Cypress称之为EZ-USBFX3.该芯片性能强劲,功能强大,接口简单,非常适合用于各种需要高速数 ...
- C/C++之文件打开方式差别
一.引言 在上一篇中,需要获取文件的大小,但是获取的文件大小与从文件中读取的数据大小总是对不上(10行数据,文件大小是129,但是读取数据是119),因此,实现的服务器总是出现这个错误:net::ER ...