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来使用.但是 ...
随机推荐
- MySQL 复制
第一步:为配置主数据库与备数据库 主:server_id = 1 log_bin = E:\mysql_log_bin #复制事实上是二进制文件在备库上的重做,所以要支持二进制文件. 备: ...
- windows平台发消息到非UI线程.
下面的代码是介绍如何在windows平台发消息到非UI线程. 主要是'PeekMessage || GetMessage' 这两个API的应用. 当他们被调用的时候,如果当前线程还没有消息循环,就会创 ...
- hdu 1394 zoj 1484 求旋转序列的逆序数(并归排序)
题意:给出一序列,你可以循环移动它(就是把后面的一段移动到前面),问可以移动的并产生的最小逆序数. 求逆序可以用并归排序,复杂度为O(nlogn),但是如果每移动一次就求一次的话肯定会超时,网上题解都 ...
- Codeforces Round #242 (Div. 2) <A-D>
CF424 A. Squats 题目意思: 有n(n为偶数)个x和X,求最少的变换次数,使得X的个数为n/2,输出变换后的序列. 解题思路: 统计X的个数ans,和n/2比較,少了的话,须要把n/2- ...
- #ifdef _cplusplus
时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus extern "C" { #endif //一段代码 #ifdef __cplusplus } #en ...
- SharePoint 2007 (MOSS/WSS) - how to remove "Download a Copy" context menu from a Document Library
One of my friend and colleague asked me this question. I found it tricky and a good post for my blog ...
- sql 查询所有数据库、表名、表字段总结
ms sql server 1.查询所有表select [id], [name] from [sysobjects] where [type] = 'u' order by [name]2.查询所有数 ...
- MySql每月增加一个分区以及查询所有分区
create PROCEDURE Usp_Partition() BEGIN DECLARE _time datetime; DECLARE num int; DECLARE _p VARCHAR(2 ...
- ORACLE备份手记
嘛的,最近一直写EPOLL的游戏服务端搞的头晕,BOSS说了要备份ORACLE,由于DBA离职了,搞这个事情搞的很蛋疼,关掉实例后备份数据库各种连接不到实例,本来今晚要完成泡泡堂游戏的DX版的,郁闷 ...
- HTML+CSS笔记 CSS中级 颜色&长度值
颜色值 在网页中的颜色设置是非常重要,有字体颜色(color).背景颜色(background-color).边框颜色(border)等,设置颜色的方法也有很多种: 1.英文命令颜色 语法: p{co ...