ThreadLocal 可以让用户很方便的保存线程隔离的变量,每个线程只能保存一份,多次set新值会覆盖旧值。下面分几个角度来分析一下ThreadLocal。
使用方法
简单附上使用方法:
public class TestThreadLocal { public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static Object lock = new Object(); public static void main(String[] args) { threadLocal.set("a string run in main"); Thread t = new Thread(new Runnable() { @Override public void run() { TestThreadLocal.threadLocal.set("a string run in sub"); synchronized (TestThreadLocal.lock){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("sub thread get: "+threadLocal.get()); } }); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (TestThreadLocal.lock){ lock.notify(); } System.out.println("main thread get: " +threadLocal.get()); } }
运行结果
main thread get: a string run in main sub thread get: a string run in sub Process finished with exit code 0
可以看到两个线程间设置的值互不影响,均可以取出正确值。
源码解析
ThreadLocal主要有3个方法,get,set,remove。我们只要了解了插入的原理,另外两个自然不攻自破,附上set的代码
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
主要结构是ThreadLocalMap,把它的数据部分以及set方法贴出来。
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<ThreadLocal<?>> { /** 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 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(); }
使用之前例子中的情况,可以看到,当我们调用set方法时,实际是以threadLocal变量本身和这个“a string run in main”字符串生成一个Entry,根据对象的hash值计算位置,放到table数组中。
一些细节
线程安全性。
ThreadLocal变量是存储在线程对象中的,即对于同一个ThreadLocal变量,在不同的线程中操作时,修改的不是同一个内存空间,因此ThreadLocal是线程安全的类。
泛化存储类型
ThreadLocal<Integer> 和 ThreadLocal<String>都是放在同一个Thread相关联的map中,类型不一样怎么存储?因此需要在调用set方法的时候将存入的对象转为Object对象,在调用get方法的时候再转换回来。
如何保证hash冲突后功能的正确性(重点)
首先我们可以看到THreadLocal处理冲突的算法:如果hash冲突了,ThreadLocal采取的是线性探测法。
考虑set方法:
从hash值的位置往后寻找到最近的一个可用位置(可用即当前位置为null,table结尾位置也不为null的话将从位置0继续查找,直到找到)。
考虑get方法:
调用get方法时,如果hash值对应的位置非空,但存储的引用和当前的ThreadLocal对象不一致,那么还有可能是因为冲突了存储在这个位置以后,从当前位置往后查找,依次对比是否是当前ThreadLocal对象,找到则返回,遇到null则说明没找到。
这对ThreadLocal提出了两点要求:
table不能是满的,否则get一个未set的ThreadLocal对象将死循环。
这个问题好解决,ThreadLocal的rehash因子是2/3,因此无论何时table必定有位置的值是null。具有相同hash值的对象,存储必须是连续的,即中间不能有null间隔开来。这就要求在remove一个对象的时候,需要将这个空位以后直到null中间的值都重新计算位置。
弱引用
java中GC的方法是判断对象的引用计数是否等于0,等于0则回收这个对象。正常情况下我们调用如下代码将产生强引用,两句代码执行过后,threadlocal指向的对象的引用计数是2。弱引用的作用就是不参与引用计数的计算。使用弱引用保证了当使用者将ThreadLocal对象置空或指向一个新的对象时,旧对象能得到被GC的机会。
同时我们也就可以解释ThreadLocal代码中多处replaceStaleEntry,expungeStaleEntry(清除过期节点)的原因。
ThreadLocal<String> threadlocal = new ThreadLocal<>(); ThreadLocal<String> threadlocal2 = threadlocal;
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,它和ThreadLocal不同的是,父线程保存的InheritableThreadLocal变量可以在子线程中存留。这个是如何实现的呢?
从以下Thread类的代码可以看到(最后几行),线程类中有一个成员变量inheritableThreadLocals,在构造函数中,会调用init方法,里面以父类的inheritableThreadLocals对象为模版构造了一份子类的inheritableThreadLocals。
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
作者:msrpp
链接:https://www.jianshu.com/p/4ba09bfad028
共同学习,写下你的评论
评论加载中...
作者其他优质文章