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(); } } } } 复制代码
运行结果:
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确实是可以达到线程隔离机制,保证了变量的安全性
ThreadLocal是为每一个线程创建一个单独的变量副本,所以每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。从其几个方法入手
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直接存储即可
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()方法
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值,定位哈希表的位置:
1、若此位置是空的,就创建一个Entry对象放在此位置上,调用cleanSomeSlots()方法清除key为null的旧entry,若没有要清除的旧entry则判断是否需要扩容
2、若此位置已经有Entry对象了,如果这个Entry对象的key正好是所要设置的key或key为null,则替换value值
3、若此位置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