概述
介绍了JDK源码中,ThreadLocal对象的细节点,以加深对其内部原理的认识。夯实读者Java基础知识。\
ThreadLocal是线程变量,是一个以自身为键,线程数据为值的类map存储结构。也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
它其实是在每个线程中都创建一个副本数据,这样每个线程在执行时,可以同时使用自己的变量,并且它们之间互不影响,彼此不存在多线程数据共享的问题。
细节分析
基本介绍其使用原因
通常我们在执行多线程并发的时候,通过使用加锁的方式来保证线程之间的数据共享不会出错。然而,加锁后,使得线程之间的执行是顺序执行,即一个线程
执行时,必须首先获得锁,若此时锁被其他线程使用,则其必须阻塞等待。这也就是人们常说的:在多线程访问的时候,为了解决线程安全问题,
使用 synchronized 关键字来实现线程同步的可以解决多线程并发访问的安全问题,但是在这种解决方案存在有性能问题。这种情况下,多个线程使用的
是同一套变量数据,必须保证线程之间的访问不受到影响。这样使用的结果就是,最终所有线程执行完所花费的时间比较长。ThreadLocal可以解决多线程之间花费时间长的问题。因为该类会在每个线程执行时,会为每个线程都创建一个变量副本。每个线程都拥有了自己独立的副本数据集,
竞争条件被彻底消除了,此时就可以不用使用synchronized进行线程同步,同时也能最大限度的使用系统资源,由CPU调度并发执行。各个线程之间对数据的
增删改都不会干扰其他的线程使用。相比于多线程之间加synchronized同步锁,减少数据的执行时间,但是此种方式,需要更多的存储空间。这也就是以空间换取时间的思想。
源码分析
1.构造函数-创建实例
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } /** * Creates a thread local variable. * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { }
可以通过无参数构造函数创建,也可以使用静态函数withInitial获取一个SuppliedThreadLocal实例。
2.设置数据和获取数据源码
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * 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) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
从中可知其数据保存在ThreadLocalMap对象中,若map存在时直接保存,不存在时先创建一个,获取时,若map不存在,则获取默认数据。
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,
initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。具体如下:void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 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; } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
通过源码分析可以看出,通过获取和设置 Thread内的 threadLocals变量,而这个变量的类型就是 ThreadLocalMap,
这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。3.数据存储结构(ThreadLocalMap)
其内部是以数组作为存储结构
private Entry[] table
,其中Entry 继承了弱引用,具体如下:static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * 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); } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16;
从代码中可以看出,内部数组的初始容量为16,根据需要会扩容,每次扩容为上一次的2倍:
/** * Double the capacity of the table. */ private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } /** * The next size value at which to resize. */ private int threshold; // Default to 0 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Re-pack and/or re-size the table. First scan the entire * table removing stale entries. If this doesn't sufficiently * shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); }
根据threshold和load factor决定关系决定扩容情况。load factor是2/3,扩容的条件为:size>= threshold - threshold/4。set每次调用后
会决定是否扩容。数据的增删都是通过此内部类处理的:
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * Remove the entry for key. */ 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)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
总结
- 能够以空间换取时间,解决多线程同步造成性能差的影响。但是两者的应用领域不同,同步机制是为了对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;
而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。 - 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
- ThreadLocal也有它的缺点,就是造成内存泄露。因为置为null的threadlocal对应的Object Value无法及时回收。(可以定时手动调用ThreadLocalMap的remove方法清理)
- threadLocal中不存入大量数据时候,GC可以自动回收,所以正常情况下,不会引起较大的内存泄漏的问题。
- threadLocal应用前景广泛,例如,数据库连接和Session会话管理的方面。
实例
未完待续。。。