深入理解ThreadLocal
2018-03-19 10:55
537 查看
深入理解ThreadLocal
ThreadLocal 主要用来提供线程局部变量,与线程同步不同,线程同步是多个线程共享同一个变量,而ThreadLocal是为每一个线程维护一个线程的副本。ThreadLocal定义了四个方法:
get():返回此线程局部变量的当前线程副本中的值。
initialValue():返回此线程局部变量的当前线程的“初始值”。
remove():移除此线程局部变量当前线程的值。
set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
案例:
/** * Created by Administrator on 2018/3/19. */ public class ThreadLocalTest { private static ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() { // 实现initialValue() public Integer initialValue() { return 0; } }; public int nextValue() { threadlocal.set(threadlocal.get() + 1); return threadlocal.get(); } public static void main(String[] args) { ThreadLocalTest threadLocalTest = new ThreadLocalTest(); ThreadlocalThread thread1 = new ThreadlocalThread(threadLocalTest); ThreadlocalThread thread2 = new ThreadlocalThread(threadLocalTest); ThreadlocalThread thread3 = new ThreadlocalThread(threadLocalTest); thread1.start(); thread2.start(); thread3.start(); } private static class ThreadlocalThread extends Thread { private ThreadLocalTest threadLocalTest; ThreadlocalThread(ThreadLocalTest test) { this.threadLocalTest = test; } public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + " 变量值为 :" + threa 4000 dLocalTest.nextValue()); } } } }
打印结果:
set方法:
//首先获取当前线程,然后调用getMap方法获取属于该线程的map //如果存在map直接插入value,<K,V> k就是当前Threadlocal的对象引用,v就是插入的value值 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } //getMap方法: //threadLocals为线程的一个成员变量,指向当前线程的ThreadLocalMap,默认值为null,也就是说第一次调用Threadlocal的get方法时无法获取到map,需要新创建一个ThreadLocalMap ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //createMap方法: //新建一个ThreadLocalMap,然后将当前线程的threadLocals指向新创建map对象 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
通过以上源码我们可以发现,ThreadLocalMap存储的键值对是以ThreadLocal对象的引用为key,然后以变量副本为value。
get方法:
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(); }
get方法较简单,以当前的ThreadLocal的对象引用为key值,从当前线程的ThreadLocalMap中取出该key值对应的Entry,然后得到value值。注意,如果entry不存在则返回ThreadLocal初始化的值,setInitialValue()方法可以被重写,如上例。
ThreadLocalMap结构:
//一个存放变量副本的实体 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //存放所有实体的数组 private Entry[] table; //初始化方法,注意:firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)作为插入数组的位置索引。 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); } //getEntry方法,同样是首先key.threadLocalHashCode & (table.length - 1)获取数组位置,然后取出entry,如果不为空或者key值符合条件,将该实体返回,否则调用getEntryAfterMiss方法 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); } //getEntryAfterMiss方法:通过分析,该方法实际上是继续查找该key值所对应的entry,为什么需要这样?(可能是因为数组的索引可能存在冲突,在ThreadLocalMap中采用的开放定址法去存储,所以该方法即是开放定址法查找元素) private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } //expungeStaleEntry:该方法主要作用是对key值为空的value值也置空,防止内存泄露。
内存泄露分析:
假如我们将初始化方法重写:
class A { } a=new A(); public A initialValue() { return a; }
前面提到每个Thread都有一个ThreadLocal.ThreadLocalMap的map,该map的key为ThreadLocal实例,它为一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null时,GC就会回收这部分空间,但是value却不一定能够被回收,因为他还与Current Thread存在一个强引用关系,就比我此例中的a对象引用,当key为空的时候,对象a是无法被GC回收的。
所以,在ThreadLocalMap中的setEntry()、getEntry(),如果遇到key == null的情况,会对value设置为null。当然我们也可以显示调用ThreadLocal的remove()方法进行处理。
最后思考一个问题,每一个线程的ThreadLocalMap中能存在多少个元素,因为是以ThreadLocalMap的对象引用作为key,所以每一次set操作是不是仅仅是覆盖value值,不会往map中添加元素?
感谢:http://cmsblogs.com/?p=2442
相关文章推荐
- java jdbc深入理解(connection与threadlocal与数据库连接池和事务实)
- ThreadLocal深入理解2
- java多线程--深入理解threadlocal以及适用场景
- 深入理解Threadlocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解线程本地变量ThreadLocal
- 深入理解ThreadLocal看了一遍尤自觉得不够
- 深入理解----ThreadLocal的工作原理
- ThreadLocal深入理解
- Java 并发:深入理解 ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- ThreadLocal深入理解与内存泄露分析
- 深入理解ThreadLocal
- 深入理解ThreadLocal
- 深入理解ThreadLocal的"内存溢出"
- 深入理解ThreadLocal
- 通过一个工具类更深入理解动态代理和Threadlocal
- 深入理解ThreadLocal