阅读总结:

  ThreadLocal内部使用静态map存储,每个变量对应一个hashcode,不需要指定key值,后台动态生成,good!

  每个变量ThreadLocal内部分配Entry,获取值时,通过变量找到Entry,找到对应hashcode,获取值;

  设置值同理。

  init部分,有点晕忽,写的乱七八糟,查了下源代码,其实就是线程初始化的时候,新建了个ThreadLocalMap变量,和什么子线程父线程木有任何关系。

  

在阅读《Java Concurrency In Practice》时,书中提到ThreadLocal是一种更为规范常用的Thread Confine方式。于是想仔细分析一下ThreadLocal的实现方式。曾经转载了一篇关于ThreadLocal的文章:hi.baidu.com/gefforey520/blog/item/c3bb64fa4ad1779358ee902c.html,其中提到ThreadLocal的实现方式是声明一个Hashtable,然后以Thread.currentThread()为key,变量的拷贝为value。今天阅读源码才知道实现方式已经大为改变,下面来看代码。
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached to each
* thread (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
* objects act as keys, searched via threadLocalHashCode. This is a custom
* hash code (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals are used
* by the same threads, while remaining well-behaved in less common cases.
*/
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);
}
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
ThreadLocal只有三个变量,从构造函数知道,在创建一个ThreadLocal实例时,只是调用nextHashCode方法将nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。 因此ThreadLocal实例的变量只有threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例。
再来看其get方法:
/**
* Returns the value in the current thread's copy of this thread-local
* variable. If the variable has no value for the current thread, it is
* first initialized to the value returned by an invocation of the
* {@link #initialValue} method.

* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
return setInitialValue();
}
其中调用getMap(Thread t)返回ThreadLocalMap,ThreadLocalMap是内部静态类,部分代码如下:
/**
* ThreadLocalMap is a customized hash map suitable only for maintaining
* thread local values. No operations are exported outside of the
* ThreadLocal class. The class is package private to allow declaration of
* fields in class Thread. To help deal with very large and long-lived
* usages, the hash table entries use WeakReferences for keys. However,
* since reference queues are not used, stale entries are guaranteed to be
* removed only when the table starts running out of space.
*/
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
Entry继承WeakReference,通过其注释并结合WeakReference的功能,我们知道:一旦没有指向 key 的强引用, ThreadLocalMap 在 GC 后将自动删除相关的 entry。ThreadLocalMap采用数组来保存Entry,并且Entry中以ThreadLocal为key,初始大小为16.
        接着看ThreadLocalMap的constructor:
/**
* 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);
}
/**
* Construct a new map including all Inheritable ThreadLocals from given
* parent map. Called only by createInheritedMap.

* @param parentMap
*            the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal key = e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
ThreadLocalMap有两个构造函数,可以直接传入ThreadLcoal-value对,也可以传入一个ThreadLocalMap,传入ThreadLocalMap的时候,会依次将其Entry存放在table中。接着来分析get方法:
/**
* Get the entry associated with key. This method itself handles only
* the fast path: a direct hit of existing key. It otherwise relays to
* getEntryAfterMiss. This is designed to maximize performance for
* direct hits, in part by making this method readily inlinable.

* @param key
*            the thread local object
* @return the entry associated with key, or null if no such
*/
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);
}
通过实例变量threadLocalHashCode算出下标,然后返回其值。set和remove方法类似。
继续看ThreadLocal类的get方法,通过getMap(Thread t)返回ThreadLocalMap,然后从ThreadLocalMap中通过getEntry(ThreadLocal key) 取出值。下面继续看getMap(Thread t)方法:
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.

* @param t
*            the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出其返回的是线程的一个实例变量。由此可知Thread类也持有ThreadLocalMap,这样每个线程的变量都存放在自己的ThreadLocalMap中,可谓名符其实。
继续看Thread类如何操作ThreadLocalMap:
/*
* 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变量,
/**
* Initializes a Thread.

* @param g
*            the Thread group
* @param target
*            the object whose run() method gets called
* @param name
*            the name of the new Thread
* @param stackSize
*            the desired stack size for the new thread, or zero to indicate
*            that this parameter is to be ignored.
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */

/*
* If there is a security manager, ask the security manager what to
* do.
*/
if (security != null) {
g = security.getThreadGroup();
}

/*
* If the security doesn't have a strong opinion of the matter use
* the parent thread group.
*/
if (g == null) {
g = parent.getThreadGroup();
}
}

/*
* checkAccess regardless of whether or not threadgroup is explicitly
* passed in.
*/
g.checkAccess();

/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}

g.addUnstarted();

this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;

/* Set thread ID */
tid = nextThreadID();
}
在init方法中,存在着对inheritableThreadLocals的操作:
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
而ThreadLocal的createInheritedMap方法则是调用ThreadLocalMap类传入ThreadLocalMap参数的构造函数。
也就是说在Thread类中,当前线程会调用init方法去初始一个线程,而在init方法中,会将当前线程的inheritableThreadLocals拷贝给等待初始化的线程。这让我联想起unix/linux系统中,父线程会调用fork()函数生成一个子线程,而且会把父线程大部分的信息拷贝给子线程。
         最后来看Thread类的exit方法:
/**
* This method is called by the system to give a Thread a chance to clean up
* before it actually exits.
*/
private void exit() {
if (group != null) {
group.remove(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
在线程真正终止前会执行这个方法,这个方法会把threadLocals和inheritableThreadLocals指向null。但我在Thread类中并没有看到对threadLocals的赋值,应该是通过ThreadLocal来设置的。
        写了个简单的Thread测试程序,只是想跟踪一下上述两个ThreadLocalMap变量的状态:
public class TimePrinter extends Thread {

public void run() {
while (true) {
try {
System.out.println(new Date(System.currentTimeMillis()));
} catch (Exception e) {
System.out.println(e);
}
}
}

static public void main(String args[]) {
TimePrinter tp1 = new TimePrinter();
tp1.start();
ThreadLocal t2 = new ThreadLocal();
t2.set("aaaaaaaaaaaaaaaaaaaaaaaa");
}
}
可以看到,启动一个线程,不停打印系统时间,然后通过ThreadLocal给当前线程添加一份字符串,观察有:

inheritableThreadLocals中有一个Entry,但value为null,threadLocals中有三个Entry,其中两个value不明,一个为ThreadLocal设置的值。不过我实在不知道其他三个Entry值是如何设置的,留个疑问。
总结:ThreadLocal实例只有一个threadLocalHashCode值,ThreadLocal给各个线程设置的值都是存在各个线程threadLocals里 。相比Hashtable的实现方式,现在的方式更为合理。当一个线程终止时,其inheritableThreadLocals和threadLocals均被置为null,于是通过TreadLocal也就无法访问这个线程;而当ThreadLocal被设置为null时,Thread里threadLocals就会移除key为ThreadLocal的Entry。Hashtable的实现方式则无法实现这一点。最为关键的是Hashtable的实现需要同步,所带来的性能损耗是很大的,而现在的方式则不需要同步。性能提升很大。

ThreadLocal源码分析(转)的更多相关文章

  1. Java多线程学习之ThreadLocal源码分析

    0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...

  2. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  3. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

  4. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  5. 并发-ThreadLocal源码分析

    ThreadLocal源码分析 参考: http://www.cnblogs.com/dolphin0520/p/3920407.html https://www.cnblogs.com/coshah ...

  6. ThreadLocal源码分析-黄金分割数的使用

    前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对T ...

  7. ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解

    本文脉路: 概念阐释 ---->  原理图解  ------> 源码分析 ------>  思路整理  ----> 其他补充. 一.概念阐述. ThreadLocal 是一个为 ...

  8. Java -- 基于JDK1.8的ThreadLocal源码分析

    1,最近在做一个需求的时候需要对外部暴露一个值得应用  ,一般来说直接写个单例,将这个成员变量的值暴露出去就ok了,但是当时突然灵机一动(现在回想是个多余的想法),想到handle源码里面有使用过Th ...

  9. Java核心复习—— ThreadLocal源码分析

    ThreadLocal,叫做线程本地存储,也可以叫做线程本地变量.ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量. 一.如何使用 class Acce ...

  10. ThreadLocal源码分析与实践

    ThreadLocal是什么? ThreadLocal是一个线程内部存储类,提供线程内部存储功能,在一个ThreadLocal对象中,每一个线程都存储各自独立的数据,互不干扰 示例如下: public ...

随机推荐

  1. javascript痛点之四this的指向问题

    先看以下例子 1.我们直接调用this看看指向的是谁 alert(this);//指向window 2.在函数中直接调用看看指向的是谁 function fn(){ alert(this); } fn ...

  2. django favicon配置

    其实网站加一个图标,在/static/images/里面放置favicon.ico 1. 直接url里修改 from django.views.generic.base import Redirect ...

  3. Docker Daemon 参数最佳实践

    1. Docker Daemon 配置参数 限制容器之间网络通信 在同一台主机上若不限制容器之间通信,容器之间就会暴露些隐私的信息,所以推荐关闭 docker daemon –icc=false 使用 ...

  4. 网页中区分IE各版本

    CSS Hack 直接在CSS文件中写CSS Hack是非常直观的区分方法.区分不同IE版本的hack代码为 #content{ background:red; /* 所有浏览器 */ backgro ...

  5. java源码学习(二)Integer

    Integer类包含了一个原始基本类型int.Integer属性中就一个属性,它的类型就是int. 此外,这个类还提供了几个把int转成String和把String转成int的方法,同样也提供了其它跟 ...

  6. Dockerfile命令详解(超全版本)

    制作Dockerfile为Docker入门学习的第一步(当然,除了环境搭建). 本文收集.整理了官网关于制作Dockerfile的全部命令(除SHELL没整理,这个就不弄了),可帮助大家快速进入Doc ...

  7. SevenZipSharp的入门教程(包含如何加密压缩,解密压缩)

    (一)为什么选择7z              7z 是一种主流高效的压缩格式,它拥有极高的压缩比.在计算机科学中,7z是一种可以使用多种压缩算法进行数据压缩的档案格式.该格式最初被7-Zip实现并采 ...

  8. Kafka 源代码分析之MessageSet

    这里分析MessageSet类 MessageSet是一个抽象类,定义了一条log的一些接口和常量,FileMessageSet就是MessageSet类的实现类.一条日志中存储的log完整格式如下 ...

  9. href 和 src 区别

    去网上百度了一下,感觉还是没有清楚的定义,所以自己稍稍的总结了一下: 1 html标签分为行类元素 和块元素 在加空元素(也可以叫做替换元素img input iframe):当元素为替换元素的时候, ...

  10. python - bilibili(四)抓包数据乱码

    上一篇文章中不知道大家发现端倪木有,两张照片对比很明显发现第一张是信息很明显的,第二张是乱码的. 为什么会出现这种情况?细心的童鞋可能发现是我们发送给服务器的请求连接的数据不同: 第一张图的信息是{& ...