java concurrency: ThreadLocal及其实现机制
转载:http://shmilyaw-hotmail-com.iteye.com/blog/1703382
ThreadLocal概念
从字面上来理解ThreadLocal,感觉就是相当于线程本地的。我们都知道,每个线程在jvm的虚拟机里都分配有自己独立的空间,线程之间对于本地的空间是相互隔离的。那么ThreadLocal就应该是该线程空间里本地可以访问的数据了。ThreadLocal变量高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。
很多人看到这里会容易产生一种错误的印象,感觉是不是这个ThreadLocal对象建立了一个类似于全局的map,然后每个线程作为map的key来存取对应线程本地的value。你看,每个线程不一样,所以他们映射到map中的key应该也不一样。实际上,如果我们后面详细分析ThreadLocal的代码时,会发现不是这样的。它具体是怎么实现的呢?后面的详细实现分析部分会讲到这个部分。先别急,看看它是怎么用的吧。
应用和好处
我们在多线程的开发中,经常会考虑到的策略是对一些需要公开访问的属性通过设置同步的方式来访问。这样每次能保证只有一个线程访问它,不会有冲突。但是这样做的结果会使得性能和对高并发的支持不够。在某些情况下,如果我们不一定非要对一个变量共享不可,而是给每个线程一个这样的资源副本,让他们可以独立都各自跑各自的,这样不是可以大幅度的提高并行度和性能了吗?
还有的情况是有的数据本身不是线程安全的,或者说它只能被一个线程使用,不能被其他线程同时使用。如果等一个线程使用完了再给另外一个线程使用就根本不现实。这样的情况下,我们也可以考虑用ThreadLocal。一个典型的情况就是我们连接数据库的时候通常会用到连接池。而对数据库的连接不能有多个线程共享访问。这个时候就需要使用ThreadLocal了。一个典型的用法如下:
- private static ThreadLocal<Connection> connectionHolder =
- new ThreadLocal<Connection>() {
- public Connection initialValue() {
- return DriverManager.getConnection(DB_URL);
- }
- };
- pubic static Connection getConnection() {
- return connectionHolder.get();
- }
ThreadLocal类本身定义了有get(), set()和initialValue()三个方法。前面两个方法是public的,initialValue()是protected的,主要用于我们在定义ThreadLocal对象的时候根据需要来重写。这样我们初始化这么一个对象在里面设置它的初始值时就用到这个方法。
ThreadLocal变量因为本身定位为要被多个线程来访问,它通常被定义为static变量。除了这个示例,在一些开源的j2ee容器以及spring框架中都有应用到。网上可以找到大量介绍的东西,这里就不在赘述。
具体实现细节分析
Thread和ThreadLocal的关系
好吧,现在进入刨根究底时间。ThreadLocal它到底是怎么实现的呢?我们先看看Thread本身的定义。在Thread.java的声明代码中,我们可以看到有这么一部分代码:
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
- /*
- * InheritableThreadLocal values pertaining to this thread. This map is
- * maintained by the InheritableThreadLocal class.
- */
- ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
这就说明了其实每个Thread本身就包含了两个ThreadLocalMap对象的引用。这一点非常重要。以后每个thread要访问他们的local对象时,就是访问存在这个ThreadLocalMap里的value。
ThreadLocalMap
那么这个ThreadLocalMap是个什么东西呢?从字面上可以猜出来,它是一个map。没错,一个map。在ThreadLocal.java中,它是一个内部类。它是以ThreadLocal为key,我们存储的对象为Value的map. 下面是它被删节后的部分定义代码:
- static class ThreadLocalMap {
- /**
- * The entries in this hash map extend WeakReference, using
- * its main ref field as the key (which is always a
- * ThreadLocal object). Note that null keys (i.e. entry.get()
- * == null) mean that the key is no longer referenced, so the
- * entry can be expunged from table. Such entries are referred to
- * as "stale entries" in the code that follows.
- */
- static class Entry extends WeakReference<ThreadLocal> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal k, Object v) {
- super(k);
- value = v;
- }
- }
- /**
- * The initial capacity -- MUST be a power of two.
- */
- private static final int INITIAL_CAPACITY = 16;
- /**
- * The table, resized as necessary.
- * table.length MUST always be a power of two.
- */
- private Entry[] table;
- /**
- * The number of entries in the table.
- */
- private int size = 0;
- /**
- * The next size value at which to resize.
- */
- private int threshold; // Default to 0
- /**
- * Set the resize threshold to maintain at worst a 2/3 load factor.
- */
- private void setThreshold(int len) {
- threshold = len * 2 / 3;
- }
- /**
- * Increment i modulo len.
- */
- private static int nextIndex(int i, int len) {
- return ((i + 1 < len) ? i + 1 : 0);
- }
- /**
- * Decrement i modulo len.
- */
- private static int prevIndex(int i, int len) {
- return ((i - 1 >= 0) ? i - 1 : len - 1);
- }
- /**
- * Construct a new map initially containing (firstKey, firstValue).
- * ThreadLocalMaps are constructed lazily, so we only create
- * one when we have at least one entry to put in it.
- */
- 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);
- }
- }
这里面牵涉到一个map的实现细节。这里面封装了一个Entry的列表,在Entry里存放的就是key和value。具体是如何从key映射到value的方法和通用的HashMap实现方法类似,在这里就不在赘述。主要知道有了这么一个map,我们给它一个ThreadLocal的对象,它就可以找到对应的value.
从get()入手
我们看看get方法的实现以及它关联的方法:
- public T get() {
- Thread t = Thread.currentThread(); // 获得当前的线程
- ThreadLocalMap map = getMap(t); //取得当前线程关联的map
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();
- }
在代码中间我增加了一些注释。这里比较有意思的一个地方就是getMap()方法。我们首先在获得当前线程的情况下,然后去取得当前线程的ThreadLocalMap。getMap方法做的就是取得ThreadLocalMap这个事。它的定义如下:
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
看到这里,我想各位已经明白了。原来get方法就是获取到当前的线程,在找到这个线程本身关联的map来折腾。你想想,既然每个线程都有各自独立的map,我也只是针对线程本身的map来操作,肯定相互之间不会有干扰了。
get()方法后面的map.getEntry()方法,无疑就是通过map来取这个对应的封装值了。Entry的实现里对这个要访问的值做了一点封装,所以后面返回的是e.value.map.getEntry()方法的实现如下:
- 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);
- }
它就是一个查找和映射的过程,具体的细节和HashMap差不多,这里就不做重点说了。
我们再来看后面的return setInitialValue();这一句是在如果前面找到的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;
- }
它调用initialValue方法获得初始值。然后判断情况,是我们映射的实体为空呢还是map为空,如果实体为空的话,我们就直接根据得到的初始值给它设上去,否则我们就新建一个map。createMap()的方法就比较简单,就是一个直接的new:
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
从前面这两部分的代码,我们可以看到。ThreadLocal中的get方法在首先调用get()方法的时候,会去调用initialValue()方法获取一下初始值。这也就是为什么前面说到推荐我们覆写initialValue()方法来设置自己期望的值。另外,在这里也会为每个线程建立它本地的map对象。
再看set()
把前面get()方法的流程理清之后,再来看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);
- }
基本上和get()的差不多,就不啰嗦了。
一个有意思的地方
为什么要通常将ThreadLocal对象声明为static的呢?一方面是为了多个线程共享方便。另外,由于每个Thread都有这么个ThreadLocalMap对象的引用,每次在ThreadLocal中执行Get方法的时候,实际上就是根据当前线程来获取它的ThreadLocalMap对象。再将这个ThreadLocal对象为Key来查找对应的值。因为每个Thread各自的ThreadLocalMap,所以相当于每个对象对应这么一个同样的ThreadLocal对象key值,来放一份自己本身的拷贝。
我们可能还有一个疑问就是既然如果我们声明一个ThreadLocal对象相当于每个关联访问的Thread有了一个该对象对应的key和value对,为什么每个对象要放这么一个Map呢?这是考虑到如果有多个ThreadLocal对象在被多个线程使用的情况。ThreadLocal类中间有这么一部分代码:
- private final int threadLocalHashCode = nextHashCode();
- /**
- * The next hash code to be given out. Updated atomically. Starts at
- * zero.
- */
- private static AtomicInteger nextHashCode =
- new AtomicInteger();
- /**
- * The difference between successively generated hash codes - turns
- * implicit sequential thread-local IDs into near-optimally spread
- * multiplicative hash values for power-of-two-sized tables.
- */
- private static final int HASH_INCREMENT = 0x61c88647;
- /**
- * Returns the next hash code.
- */
- private static int nextHashCode() {
- return nextHashCode.getAndAdd(HASH_INCREMENT);
- }
ThreadLocal中的static变量nextHashCode相当于一个全局的私有变量,但是threadLocalHashCode是针对每个对象的实例成员,每次它被初始化的时候都要调用nextHashCode()方法。这个方法是static的,在成员初始化的时候也就运行一次。它就是为了将这个值增加一段,保证这个对象的threadLocalHashCode和其他ThreadLocal对象的不一样。因为最终将ThreadLocal对象映射到map中的值是用的threadLocalHashCode。所以,当我们多个线程要访问多个ThreadLocal变量的时候,每个变量映射到的就是ThreadLocalMap中不同的项。
总结
每个线程都有一个map,这个map里存的就是和该线程关联的本地数据。可能这个map是空的。在通过访问ThreadLocal的方法时,通过和ThreadLocal对象建立关联来映射到对应的本地对象。这个ThreadLocal对象相当于是map的key,放的本地变量的值相当于map里的value.ThreadLocal相当于一个帮助类,为每个访问的线程建立本地的拷贝数据。
java concurrency: ThreadLocal及其实现机制的更多相关文章
- Java Concurrency - ThreadLocal, 本地线程变量
		共享数据是多线程应用最常见的问题之一,但有时我们需要为每个线程保存一份独立的变量.Java API 提供了 ThreadLocal 来解决这个问题. 一个 ThreadLocal 作用的例子: imp ... 
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题
		主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ... 
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题[转]
		主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的一点 ... 
- java并发:线程同步机制之ThreadLocal
		1.简述ThreadLocal ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程.ThreadLocal是一个线程级别的局部变量 ... 
- Java 内存区域和GC机制分析
		目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ... 
- Java中ThreadLocal的设计与使用
		早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择.使用这个工具类可以很简洁地编写出优美的多线程程 ... 
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
		转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ... 
- 【转载】Java系列笔记(3) - Java 内存区域和GC机制
		Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ... 
- ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因
		引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ... 
随机推荐
- 树莓派高级GPIO库,wiringpi2 for python使用笔记(一)安装
			网上的教程,一般Python用RPi.GPIO来控制树莓派的GPIO,而C/C++一般用wringpi库来操作GPIO,RPi.GPIO过于简单,很多高级功能不支持,比如i2c/SPI库等,也缺乏高精 ... 
- CCNP路由实验(3) -- 路由控制
			1.用distribute-list过滤路由在不同协议里的用法 在RIP里 在EIGRP里 在OSPF里 只接收奇数路由 只接收偶数路由 只接收被4整除的路由2.offset-list在不同协议里的用 ... 
- Android实现左右滑动指引效果
			本文介绍Android中实现左右滑动的指引效果. 关于左右滑动效果,我在以前的一篇博文中提到过,有兴趣的朋友可以查看:http://www.cnblogs.com/hanyonglu/archive/ ... 
- 一套手写ajax加一般处理程序的增删查改
			倾述下感受:8天16次驳回.这个惨不忍睹. 好了不说了,说多了都是泪. 直接上代码 : 这个里面的字段我是用动软生成的,感觉自己手写哪些字段太浪费时间了,说多了都是泪 ajax.model层的代码: ... 
- 刚开始学HTML自己做的,求大神些多多指教。
			!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> ... 
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite
			SQLite是一种转为嵌入式设备设计的轻型数据库,其只有五种数据类型,分别是: NULL: 空值 INTEGER: 整数 REAL: 浮点数 TEXT: 字符串 BLOB: 大数据 在SQLite中, ... 
- Maven+SpringMVC+MyBatis 上传图片
			上传文件我一直都觉得很难,好吧,所有涉及文件操作的我都觉得不容易.然后今天尝试了从网页上传图片保存到服务器.这个例子的前提是搭建好了服务器端框架:Maven+Spring MVC+MyBatis.当然 ... 
- 解决mac 10.11 以后 无法使用未签名第三驱动
			解决 最新版 mac 系统 无法使用未签名第三驱动 10.12.多 我的情况是 10.11.4 Beta (15E27e) 使用绿联usb网卡不正常. 下面的命令为检测驱动是否装载的一些命令.sudo ... 
- BZOJ 1452: [JSOI2009]Count(二维BIT)
			为每一个权值开一个二维树状数组. ------------------------------------------------------------------------- #include& ... 
- python 冒泡和快排,不多说【无聊】
			#-*-coding:utf8-*- import random a=[] b=[] def init_array(): for i in range(10000): v = random.randi ... 
