了解Java的Synchronized机制的大家想必都了解过,这个锁有多么的强大和美妙。它就像哪吒有三头六臂,手上拽着一堆法宝。
它可以用在类上,可以用在方法上面,甚至可以用在代码块上面。一个线程需要获取被Synchronized修饰过的方法或者对象,通常需要阻塞等待一下。
就好像大家都需要拿着电影票,挨个排好队,依次通过检票员的验证,才可以进入影厅观看电影。
当你觉得用得很顺手的时候,继续深挖原理,往往会给你当头一盆冷水。
Synchronized机制的使用:
我们都知道线程执行任务都是在一定的CPU时间片里面进行的。如果大家都阻塞等待了,那未免也太对不起现代的多核CPU了吧。
好了,说了这么多,接下来才是引入今天的主角,ThreadLocal。
为什么说她是各扫门前雪呢?道理很简单,ThreadLocal的实现机制就是在当前线程内部存储自己的一份数据,这份数据不受其他线程的干扰。
有些文章会说是在复制了一份副本存储在线程内部。
ThreadLocal主要有两个方法,get() 和 set()
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 set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
从代码不难看出,ThreadLocal内部实际上是定义了一个叫做ThreadLocalMap 的 HashMap,用于存储当前线程的数据
再往下一步,源码对TreadLocalMap有一个比较详尽的描述:
/**
* 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.
*/
意思大概是,由于Map使用WeakReference弱引用对象作为key,因为没有使用WeakRefrence的弱引用队列(即key具有唯一性)
WeakRefrence这个东东也是一个很奇妙的东西,有兴趣可以在园子里面逛逛,看看相关介绍啦,我自己也是一步步追溯下来理解的。这里就不展开了。
所以只有当Map的数据非常大,已经超出规定的内存空间的时候才会被GC掉。这样保障了单个复杂操作过程,线程中ThreadLocal数据的稳定。
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. */ // 只有当key是null的时候,才会被定义为陈旧的数据被移除
// static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } // ThreadLocalMap的构造
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);
}
...
...
};
关于陈旧数据的移除:
可以看到ThreadLocalMap实际上是一个数组结构,用于存储ThreadLocal数据。ThreadLocal内部定义了一个水位线,当内存大于这个水位线的时候,会先把陈旧的数据线GC掉;
如果GC的效果并不理想,会把水位线值升高,增大内存。
这样做的好处当然是为了避免频繁的GC和保持数据的稳定性啦。
/** * 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(); }
讲了这么一些内容,总结一下:
1. ThreadLocal实际上是用在线程内部存储数据,避免Thread争夺资源导致产生了脏的数据。
2. 其内部运用水位线的机制来控制什么时候进行内存的GC,保障数据稳定性的同时也减小系统开销
至此,ThreadLocal为什么自扫门前雪,大概有一个比较好的解释了。那就是我管我自己的事情,跟你其他人(线程)一点关系都没有!
你喜欢堆雪人,我喜欢打雪仗,大家互不干扰,各干各的。
那么ThreadLocal在实际应用中会怎么用呢?我会在下一篇:Spring的数据库的主从配置 里面进行介绍
世界上有些东西就是这么美妙,当你一层层剥开它的时候,会忍不住说,哇塞,太棒了吧。
献上杨宗纬的《洋葱》:
如果你愿意一层一层
一层地剥开我的心
你会鼻酸
你会流泪
只要你能
听到我
看到我的全心全意
原文出处:各扫门前雪的ThreadLocal