jdk源码注解中有这样一段描述:
数组
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
缓存
这个类提供线程局部变量。这些变量与其正常的对应方式不一样,由于访问一个的每一个线程(经过其get或set方法)都有本身独立初始化的变量副本。 ThreadLocal实例一般是但愿将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)
安全
须要明确的是ThreadLocal不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每一个线程处理本身的状态而引入的一个机制多线程
public class SeqCount {
private static ThreadLocal seqCount = new ThreadLocal(){
// 实现initialValue()
public Integer initialValue() {
return 0;
}
};
public int nextSeq(){
seqCount.set(seqCount.get() + 1);
return seqCount.get();
}
public void remove() {
seqCount.remove();
}
public static void main(String[] args){
SeqCount seqCount = new SeqCount();
SeqThread thread1 = new SeqThread(seqCount);
SeqThread thread2 = new SeqThread(seqCount);
SeqThread thread3 = new SeqThread(seqCount);
SeqThread thread4 = new SeqThread(seqCount);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
private static class SeqThread extends Thread{
private SeqCount seqCount;
SeqThread(SeqCount seqCount){
this.seqCount = seqCount;
}
public void run() {
try {
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName() + " seqCount :" + seqCount.nextSeq());
}
} finally {
seqCount.remove();
}
}
}
}
复制代码
运行结果:ide
Thread-0 seqCount :1
Thread-0 seqCount :2
Thread-0 seqCount :3
Thread-1 seqCount :1
Thread-1 seqCount :2
Thread-1 seqCount :3
Thread-3 seqCount :1
Thread-3 seqCount :2
Thread-3 seqCount :3
Thread-2 seqCount :1
Thread-2 seqCount :2
Thread-2 seqCount :3
复制代码
从结果能够得知,ThreadLocal确实是能够达到线程隔离机制,保证了变量的安全性this
ThreadLocal是为每个线程建立一个单独的变量副本,因此每一个线程均可以独立地改变本身所拥有的变量副本,而不会影响其余线程所对应的副本。从其几个方法入手
spa
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 经过当前线程实例获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 若map不为null,则以当前threadLocal为键,value为值存放
if (map != null)
map.set(this, value);
// 若map为null,则建立ThreadLocalMap,以当前threadLocal为键,value为值
else
createMap(t, value);
}
复制代码
获取当前线程实例,调用getMap()获取此线程的ThreadLocalMap线程
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
而后判断map是否为null,若为null则还需建立threadLocalMap,以当前threadLocal为键,value为值存放在threadLocalMap中,若不为null直接存储便可code
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程关联的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若map不为null,从map中获取以当前threadLocal实例为key的数据
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 若map为null或者entry为null,则调用此方法初始化
return setInitialValue();
}
复制代码
get方法获取当前线程关联的ThreadLocalMap。若map不为null,以threadLocal实例为key获取数据;若map为null或entry为null调用setInitialValue()方法orm
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;
}
复制代码
与set方法差很少,但多了initialValue()方法,此方法须要子类重写
protected T initialValue() {
return null;
}
复制代码
public void remove() {
// 根据当前线程获取其所关联的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
// 若map不为null,删除以当前threadLocal为key的数据
if (m != null)
m.remove(this);
}
复制代码
从ThreadLocal那几个核心方法来看,其实现都基于内部类ThreadLocalMap
// 初始化容量
private static final int INITIAL_CAPACITY = 16;
// 哈希表
private Entry[] table;
// 元素个数
private int size = 0;
// 扩容阈值(threshold = 底层哈希表table的长度 len * 2 / 3)
private int threshold;
复制代码
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
复制代码
从源码中能够得知Entry的key是Threadlocal,而且Entry继承WeakReference弱引用。注意Entry中并无next属性,相对于HashMap采用链地址法处理冲突,ThreadLocalMap采用开放定址法
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根据ThreadLocal的hashcode值,寻找对应Entry在数组中的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 若找到对应key,替换旧值返回
if (k == key) {
e.value = value;
return;
}
// 若key == null,由于e!=null确定存在entry
// 说明以前的ThreadLocal对象已经被回收
if (k == null) {
// 替换旧entry
replaceStaleEntry(key, value, i);
return;
}
}
// 建立新entry
tab[i] = new Entry(key, value);
// 元素个数+1
int sz = ++size;
// cleanSomeSlots 清除旧Entry(key == null)
// 若是没有要清除的数据,元素个数仍然大于阈值则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
复制代码
每一个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增长一个固定的大小0x61c88647。在插入过程当中先根据threadlocal对象的hash值,定位哈希表的位置:
一、若此位置是空的,就建立一个Entry对象放在此位置上,调用cleanSomeSlots()方法清除key为null的旧entry,若没有要清除的旧entry则判断是否须要扩容
二、若此位置已经有Entry对象了,若是这个Entry对象的key正好是所要设置的key或key为null,则替换value值
三、若此位置Entry对象的key不符合条件,寻找哈希表此位置+1(若到达哈希表尾则从头开始)
咱们能够发现ThreadLocalMap采用了开放定址法来解决冲突,一旦发生了冲突,就去寻找下一个空的散列地址,而HashMap采用链地址法解决冲突在原位置利用链表处理
private Entry getEntry(ThreadLocal key) {
// 定位
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 若此位置不为空且与entry的key返回entry对象
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
复制代码
理解了set,getEntry很好理解。先根据threadlocal对象的hash值,定位哈希表的位置。若此位置entry的key和查找的key相同的话就直接返回这个entry,若不符合调用getEntryAfterMiss()继续向后找,getEntryAfterMiss方法以下:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
// 找到和所需key相同的entry则返回
if (k == key)
return e;
// 处理key为null的entry
if (k == null)
expungeStaleEntry(i);
else
// 继续找下一个
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
复制代码
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 若找到所需key
if (e.get() == key) {
// 将entry的key置为null
e.clear();
// 将entry的value置为null同时entry置空
expungeStaleEntry(i);
return;
}
}
}
复制代码
定位在哈希表的位置,找到相同key的entry,调用clear方法将key置为null,调用expungeStaleEntry方法删除对应位置的过时实体,并删除此位置后key = null的实体
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将此位置的entry对象置空以及value置空
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 元素个数-1
size--;
// Rehash until we encounter null
Entry e;
int i;
// 清除此位置后key为null的entry对象以及rehash位置不一样的entry直至有位置为空为止
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
复制代码
先附上四种引用与gc关系
引用类型 | 回收机制 | 用途 | 生存时间 |
强引用 | 从不回收 | 对象状态 | JVM中止运行时 |
软引用 | 内存不足时回收 | 对象缓存 | 内存不足时终止 |
弱引用 | 对象不被引用时回收 | 对象缓存 | GC后终止 |
虚引用 | 对象不被引用时回收 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
Son son = new Son();
Parent parent = new Parent(son);
复制代码
当咱们把son置空,因为parent持有son的引用且parent是强引用,因此gc并不回收son所分配的内存空间,这就致使了内存泄露
若是是弱引用那么上述例子,GC就会回收son所分配的内存空间。而ThreadLocalMap采用ThreadLocal弱引用做为key,虽然ThreadLocal是弱引用GC会回收这部分空间即key被回收,可是value却存在一条从Current Thread过来的强引用链。所以只有当Current Thread销毁时,value才能 获得释放
那么如何有效的避免呢?
在上述中咱们能够看到ThreadLocalMap中的set/getEntry方法中,会对key为null(即ThreadLocal为null)进行判断,若是为null的话,那么是会对value置为null的。固然也能够经过调用ThreadLocal的remove方法进行释放。
ThreadLocal不是用来解决共享对象的多线程访问问题,而是为了方便每一个线程处理本身的状态而引入的一个机制。它为每个线程都提供一份变量的副本,从而实现同时访问而互不影响。另外ThreadLocal可能存在内存泄漏问题,使用完ThreadLocal以后,最好调用remove方法
www.jianshu.com/p/377bb8408…
www.jianshu.com/p/ee8c9dccc…
cmsblogs.com/?p=2442