你真的了解ThreadLocal吗?

it2024-02-23  61

一, 是什么?怎么用?

是什么?

是每个线程的本地变量,可以存储每个线程独有的变量.

怎么用?

可以为每个线程创建一个独有的变量对象

可以实现线程间的数据隔离

Spring声明式事务中使用ThreadLocal实现数据库隔离


二, 类架构

ThreadLocal属性

/** * 该值用于给ThreadLocalHashMap中存入值时线性探测插入的bucket位置 */ private final int threadLocalHashCode = nextHashCode(); /** * 下一个要给出的hashCode,每次原子性更新,从0开始 */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * hashCode增值,使用这个数字可以使key均匀的分布在2的幂次方的数组上 * 具体可以参考 https://www.javaspecialists.eu/archive/Issue164-Why-0x61c88647.html * 因为比较复杂,在这里不展开讨论 */ private static final int HASH_INCREMENT = 0x61c88647;

ThreadLocalMap属性

/** * Map的初始化容量 */ private static final int INITIAL_CAPACITY = 16; /** * 哈希表,长度始终是2的幂,原因在于2的幂-1的二进制全部为1 * 便于按位与操作 * 如16: 10000 - 1 = 1111 * 按位与后都会在数组中 */ private Entry[] table; /** * 哈希表的长度 */ private int size = 0; /** * 扩容阈值,默认为0,扩容大小为哈希表长度的2/3 */ private int threshold;

三, 实现原理

1, 为什么ThreadLocal可以实现线程隔离?

/** * 创建一个ThreadLocal,可以看出在构造器内部并没有处理任何事情 */ public ThreadLocal() {} /** * 给ThreadLocal中设置值会调用该方法 */ public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程中的变量threadLocals ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //当前线程的threadLocals变量为空,创建map设置值 createMap(t, value); } ThreadLocalMap getMap(Thread t) { //获取t的线程变量 return t.threadLocals; }

由上可以看出给ThreadLocal设置值本质上是给Thread的本地变量threadLocals变量设置值,这就是为什么ThreadLocal可以实现线程之间数据隔离


2, 增删查操作

/** * 给ThreadLocal中设置值会调用该方法 */ public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程中的变量threadLocals ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //当前线程的threadLocals变量为空,创建map设置值 createMap(t, value); } void createMap(Thread t, T firstValue) { //给线程设置变量值 t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * 设置与key相关的值,key为ThreadLocal */ private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //按位与获取插入的位置 int i = key.threadLocalHashCode & (len - 1); /** * 使用线性探测法插入值 * 从获取到的下标向后遍历,如果当前key等于数组中当前下标的key则直接修改值 * 如果数组当前下标位置为空则替换掉当前下标的entry */ 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; } } //如果i向后没有与其相同的key或者为空的位置则直接替换掉当前位置的entry tab[i] = new Entry(key, value); int sz = ++size; //从i向后没有空位,并且数量大于阈值需要rehash,删除哈希表中某些为空的entry如果size大于阈值的3/4则需要扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * 替换掉无效的entry */ private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) //从后向前寻找空位,寻找到距离staleSlot最远的一个位置 if (e.get() == null) slotToExpunge = i; // 向后遍历 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //找到了与ThreadLocal相同的key,替换值 if (k == key) { e.value = value; //替换掉当前位置的entry tab[i] = tab[staleSlot]; //交换entry tab[staleSlot] = e; // 前面没有空位,设置删除的位置 if (slotToExpunge == staleSlot) slotToExpunge = i; //删除后面为空的位置 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // 如果没有发现key,则直接替换 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // staleSlot前有空位需要删除为空的entry if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * rehash哈希表 */ private void rehash() { //删除无效的entry并且rehash哈希表 expungeStaleEntries(); // 如果数量大于等于阈值的3/4,需要扩容 if (size >= threshold - threshold / 4) resize(); } /** * 删除无效的entry并且rehash哈希表 */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } /** * 双倍扩容之前的哈希表 */ 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) { //当前位置key为空,则直接GC 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); //重新插入entry newTab[h] = e; count++; } } } //重新设置阈值和哈希表属性 setThreshold(newLen); size = count; table = newTab; } /** * 删除某些entry以减半的方式删除 */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } //无条件右移1位 /2 } while ((n >>>= 1) != 0); return removed; } /** * 删除具体位置的entry,并且rehash之后的entry */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 删除当前下标的entry tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; //从当前位置向后遍历,寻找为空的或者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); //rehash之后的位置不是当前的位置需要删除当前的entry if (h != i) { //help GC tab[i] = null; //从h向后遍历,寻找一个为空的位置 while (tab[h] != null) h = nextIndex(h, len); //插入entry tab[h] = e; } } } return i; } /** * 获取ThreadLocal中存放的值 */ public T get() { //1, 获取当前线程 Thread t = Thread.currentThread(); //2, 获取当前线程中的threadLocals变量,也就是ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { //3, 获取之前设置的值并返回 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //3, 如果当前线程的threadLocals变量为空,则还没有初始化,需要进行初始化 return 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; } /** * 初始化值为null */ protected T initialValue() { return null; } /** * 删除变量 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * 删除某个entry */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1); //从i向后遍历 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //help GC e.clear(); //删除当前entry并且rehash后面的entry expungeStaleEntry(i); return; } } }

三, 存在问题

1, 内存泄漏问题

因为使用ThreadLocal本质上是使用ThreadLocalMap,在使用完ThreadLocal后,无法手动删除ThreadLocalMap中的key(ThreadLocal的引用),所以可能会引起内存泄漏问题,但是代码在设计的时候就考虑到了这一点,所以将ThreadLocalMap中的key(ThreadLocal)设置为了弱引用(WeakReference),即很容易被GC掉,但即便如此,我们还是要在使用完后调用ThreadLocal的remove方法,手动删除ThreadLocal引用,避免内存泄漏.

2, 子线程不能访问父线程变量

可以使用InheritableThreadLocal

原理

ThreadLocal<String> local = new InheritableThreadLocal<>(); local.set("main local variable"); public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //因为InheritableThreadLocal重写了ThreadLocal的getMap方法,所以下面调用的为InheritableThreadLocal中的getMap,获取的为Thread类的inheritableThreadLocals变量 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //当前线程的threadLocals变量为空,创建map设置值 createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } //创建子线程 Thread thread = new Thread(() -> { local.set("child thread variable"); System.out.println("child thread get local variable : " + local.get()); }); public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { //获取当前线程,这里为父线程,子线程还没有被创建出来 Thread parent = currentThread(); ... //这里的parent.inheritableThreadLocals在父线程set的时候已经初始化了 if (inheritThreadLocals && parent.inheritableThreadLocals != null) //所以子类的ThreadLocal也是使用的inheritableThreadLocals,不是之前的threadLocals this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } 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<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { //调用InheritableThreadLocal对象的childValue方法,返回e.value Object value = key.childValue(e.value); Entry c = new Entry(key, value); //插入到子线程的inheritableThreadLocals,也就是将父线程的inheritableThreadLocals数据复制到子线程中 int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
最新回复(0)