java并发:线程同步机制之ThreadLocal
一、初识ThreadLocal
ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联线程。
A、当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供了一个独立初始化的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。
下图展示了实际工作机制:
解释:
- 在每个线程内部都有一个名为 threadLocals 的成员变量
- 该变量的类型为HashMap
- 其中 key为ThreadLocal变量的this引用
- value为 set方法设置的值
简而言之:每个线程的本地变量存放在线程自己的内存变量 threadLocals中
二、详述ThreadlocalMap
其类图如下:
ThreadLocal中的方法以及内部类如下图所示:
其主要方法如下:
- void set(T value)
该方法用来设置当前线程中变量的副本
- public T get()
该方法用来获取当前线程中变量的副本
- public void remove()
该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。
需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected T initialValue()
该方法是一个protected方法,ThreadLocal中的缺省实现直接返回一个null,一般用来重写。
三、示例
生成线程序列号
public class ThreadId { private static final AtomicInteger atomicInteger = new AtomicInteger(0); private static final ThreadLocalthreadId = new ThreadLocal () { @Override protected Integer initialValue() { return atomicInteger.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
四、实现机制
(1)get()方法源码
(2)set()方法源码
(3)remove()方法源码
(4)getMap()、createMap
解读:
从源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程。
当一个线程调用 ThreadLocal 的 set 方法设置变量时,当前线程的 ThreadLocalMap 里会存放一条记录,这条记录的key为ThreadLocal的弱引用,value则为设置的值。
Note:
分析:
如果当前线程一直存在且没有调用 ThreadLocal 的 remove 方法,则当前线程的 ThreadLocalMap 变量里面会存在对 ThreadLocal 变量和 value对象的引用,它们是不会被释放的,这就会造成内存泄漏。
实际情况:
ThreadLocalMap 里面的 key 是弱依赖,在 GC 的时候被回收,但是对应的 value还是会造成内存泄漏(即 ThreadLocalMap里面存在 key为 null但 value不为 null 的Entry项)。
ThreadLocalMap 的 set、get方法可以在一些时机下对这些Entry项进行清理,但这是不及时的,也不是每次都会执行,所以在一些情况下还是会发生内存漏,因此建议在使用完毕后及时调用 remove方法。
五、ThreadLocalMap
其类图如下:
ThreadLocalMap的部分源码如下:
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * 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; } /** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } /** * 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); } /** * Construct a new map including all Inheritable ThreadLocals * from given parent map. Called only by createInheritedMap. * * @param parentMap the map associated with parent thread. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal
此处重点关注一下ThreadLocalMap中的几个成员变量及方法
(1)private Entry[] table;
table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化
Entry是ThreadLocalMap的一个内部类
(2)set()方法
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be 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(); }
(3)getEntry()方法
/** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ 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); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ 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; }
(4)remove()方法
/** * 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一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。
ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。