//咱们只须要实例化一次,做为key就够了,或者使用静态。
private ThreadLocal myThreadLoca<String>l = new ThreadLocal();
// 写入
myThreadLocal.set("A thread local value");
// 读取
String threadLocalValue = myThreadLocal.get();
复制代码
在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量,实现串行化.这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析何时对变量进行读/写、何时须要锁定某个对象、何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。java
给每一个线程创建副本,互不干扰.数组
对于多线程资源共享的问题.安全
同步机制采用“以时间换空间”的方式:访问串行化,对象共享化 ; ThreadLocaI采用了“以空间换时间”的方式:访问并行化,对象独享化。数据结构
前者仅提供一份变量,让不一样的线程排队访问;然后者为每一个线程都提供了一份变量,所以能够同时访问而互不影响。多线程
// hash code
private final int threadLocalHashCode = nextHashCode();
// AtomicInteger类型,从0开始
private static AtomicInteger nextHashCode =
new AtomicInteger();
// hash code每次增长1640531527
private static final int HASH_INCREMENT = 0x61c88647;
// 下一个hash code
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
复制代码
ThreadLocal的hashcode(threadLocalHashCode)是从0开始,每新建一个ThreadLocal,对应的hashcode就加0x61c88647.工具
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
// 当前线程的ThreadLocalMap不为空则调用set方法, this为调用该方法的ThreadLocal对象
if (map != null)
map.set(this, value);
// map为空则调用createMap方法建立一个新的ThreadLocalMap, 并新建一个Entry放入该
// ThreadLocalMap, 调用set方法的ThreadLocal和传入的value做为该Entry的key和value
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 返回线程t的threadLocals属性,类型是ThreadLocalMap
}
复制代码
注意此处的threadLocals变量是一个ThreadLocalMap,是Thread的一个局部变量,所以它只与当前线程绑定。源码分析
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
复制代码
也是先得到当前线程的ThreadLocalMap变量.其实ThreadLocal类就像是一个工具类同样,核心是其内部类ThreadLocalMap.this
ThreadLocalMap是一个自定义哈希映射,仅用于维护线程本地变量值。
ThreadLocalMap是ThreadLocal的内部类,主要有一个Entry数组,Entry的key为ThreadLocal,value为ThreadLocal对应的值。
每一个线程都有一个ThreadLocalMap类型的threadLocals变量。spa
// 初始容量16
private static final int INITIAL_CAPACITY = 16;
// entry数组
private Entry[] table;
// entry 元素个数
private int size = 0;
// 扩容阀值
private int threshold; // Default to 0
// 一次set是2/3 len
private void setThreshold(int len) { threshold = len * 2 / 3; }
//下面两个方法能够向前/后获取坐标.
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0);}
private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1); }
复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
复制代码
结合示意看,entry中的key是一个弱引用.它的存在不会使得ThreadLocal变量豁免垃圾回收.当定义ThreadLocal变量的线程终止,没有强引用关联ThreadLocal变量,它就会被垃圾回收..net
说到底ThreadLocal变量只是一个key而已,能够用它去各个线程本身的ThreadLocalMap中查询,它自己并不存储信息.
固然这会形成各个线程的ThreadLocalMap中出现key为null的entry.这个在后边内存泄漏会讲到.
private void rehash() {
expungeStaleEntries(); // 调用expungeStaleEntries方法清理key为null的Entry
// 若是清理后size超过阈值的3/4, 则进行扩容
if (size >= threshold - threshold / 4)
// 大小*2
// h = k.threadLocalHashCode & (newLen - 1);
resize();
}
复制代码
cleanSomeSlots方法
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len); // 下一个索引位置
Entry e = tab[i];
if (e != null && e.get() == null) { // 遍历到key为null的元素
n = len; // 重置n的值
removed = true; // 标志有移除元素
i = expungeStaleEntry(i); // 移除i位置及以后的key为null的元素
}
} while ( (n >>>= 1) != 0);
return removed;
}
复制代码
从 i 开始,清除key为空的Entry,扫描次数是log2n
,当遍历到一个key为null的元素时,调用expungeStaleEntry清除,并将遍历次数重置。 n的值多是当前元素个数size(从增长元素操做调过来).或者整个entry长度len(从replaceStaleEntry方法调过来). 官方给出的解释是这个方法简单、快速,而且效果不错。
expungeStaleEntry 方法
// 从staleSlot开始, 清除key为空的Entry, 并将不为空的元素放到合适的位置,最后返回Entry为空的位置
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null; // 将tab上staleSlot位置的对象清空
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 遍历下一个元素, 即(i+1)%len位置的元素
(e = tab[i]) != null; // 遍历到Entry为空时, 跳出循环并返回索引位置
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 当前遍历Entry的key为空, 则将该位置的对象清空
e.value = null;
tab[i] = null;
size--;
} else { // 当前遍历Entry的key不为空
int h = k.threadLocalHashCode & (len - 1); // 从新计算该Entry的索引位置
if (h != i) { // 若是索引位置不为当前索引位置i
tab[i] = null; // 则将i位置对象清空, 替当前Entry寻找正确的位置
// 若是h位置不为null,则向后寻找当前Entry的位置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
复制代码
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 计算出索引的位置
// 从索引位置开始遍历,因为不是链表结构,所以经过nextIndex方法来寻找下一个索引位置
for (Entry e = tab[i];
e != null; // 当遍历到的Entry为空时结束遍历
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 拿到Entry的key,也就是ThreadLocal
// 该Entry的key和传入的key相等, 则用传入的value替换掉原来的value
if (k == key) {
e.value = value;
return;
}
// 该Entry的key为空, 则表明该Entry须要被清空,
// 调用replaceStaleEntry方法
if (k == null) {
// 该方法会继续寻找传入key的安放位置, 并清理掉key为空的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 寻找到一个空位置, 则放置在该位置上
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots是用来清理掉key为空的Entry,若是此方法返回true,则表明至少清理
// 了1个元素, 则这次set必然不须要扩容, 若是此方法返回false则判断sz是否大于阈值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 扩容
}
复制代码
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot; // 清除元素的开始位置(记录索引位置最前面的)
// 向前遍历,直到遇到Entry为空
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i; // 记录最后一个key为null的索引位置
// Find either the key or trailing null slot of run, whichever
// occurs first
for (int i = nextIndex(staleSlot, len); // 向后遍历,直到遇到Entry为空
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 该Entry的key和传入的key相等, 则将传入的value替换掉该Entry的value
if (k == key) {
e.value = value;
// 将i位置和staleSlot位置的元素对换(staleSlot位置较前,是要清除的元素)
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 若是相等, 则表明上面的向前寻找key为null的遍历没有找到,
// 即staleSlot位置前面的元素没有须要清除的,此时将slotToExpunge设置为i,
// 由于原staleSlot的元素已经被放到i位置了,这时位置i前面的元素都不须要清除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 从slotToExpunge位置开始清除key为空的Entry
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 若是第一次遍历到key为null的元素,而且上面的向前寻找key为null的遍历没有找到,
// 则将slotToExpunge设置为当前的位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 若是key没有找到,则新建一个Entry,放在staleSlot位置
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 若是slotToExpunge!=staleSlot,表明除了staleSlot位置还有其余位置的元素须要清除
// 须要清除的定义:key为null的Entry,调用cleanSomeSlots方法清除key为null的Entry
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
复制代码
ThreadLocal 是一个key,去各个线程本身的map里取值,不必多个.
Java 中每一个线程都有与之关联的Thread对象,Thread对象中有一个ThreadLocal.ThreadLocalMap类型的成员变量,该变量是一个Hash表.因此每一个线程都单独维护这样一个Hash表. 当ThreadLocal类型对象调用set方法时,这个set方法会使用当前线程维护的Hash表,把本身做为key, set方法的参数做为value插入到Hash表中.因为每一个线程维护的Hash表是独立的,所以在不一样的Hash表中,key值即便相同也是没问题的.
若是不使用static的ThreadLocal变量,那么当定义ThreadLocal的类建立新的实例时候,会出现多个ThreadLocal.这在大多数时候是没有意义的.
ThreadLocalMap使用ThreadLocal的弱引用做为Entry的key,若是一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value。
咱们上面介绍的get、set、remove等方法中,都会对key为null的Entry进行清除(expungeStaleEntry方法,将Entry的value清空,等下一次垃圾回收时,这些Entry将会被完全回收)。
可是若是当前线程一直在运行,而且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用练:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,致使这些key为null的Entry的value永远没法回收,形成内存泄漏。
为了不这种状况,咱们能够在使用完ThreadLocal后,手动调用remove方法,以免出现内存泄漏。
因为咱们通常把ThreadLocal变量声明成static类型,延长了它的生命周期,若是因为一些缘由,持有ThreadLocal变量声明的类没有被回收. 那么确实全部使用这个ThreadLocal变量的线程中的ThreadLocalMap中的entry中的value都不会被回收(key != null). 这确实会形成问题,尤为当你的工做线程是复用的,自己永远不会终止,好比线程池/tomat的工做线程. 更糟糕的是,没法回收的value对应的类自己,和它的类加载器也没法被回收.这个类加载器全部加载的全部的类的数据会存留在永久区.形成永久区内存泄漏(permgen leak)
使用后remove()是一个好习惯.
InheritableThreadLocal类是ThreadLocal类的子类. 子线程会copy父线程的值,造成本身的副本(浅拷贝).
1.对于可变对象:
2.对于不可变对象: 不可变对象因为每次都是新对象, 因此不管父线程初始化与否,子线程和父线程都互不影响。
从上面两条结论可知,子线程只能经过修改可变性(Mutable)对象对主线程才是可见的,即才能将修改传递给主线程,但这不是一种好的实践,不建议使用,为了保护线程的安全性,通常建议只传递不可变(Immuable)对象,即没有状态的对象。