各扫门前雪的ThreadLocal

java 文章 2022-07-23 21:00 0 全屏看文

了解Java的Synchronized机制的大家想必都了解过,这个锁有多么的强大和美妙。它就像哪吒有三头六臂,手上拽着一堆法宝。

它可以用在类上,可以用在方法上面,甚至可以用在代码块上面。一个线程需要获取被Synchronized修饰过的方法或者对象,通常需要阻塞等待一下。

就好像大家都需要拿着电影票,挨个排好队,依次通过检票员的验证,才可以进入影厅观看电影。

当你觉得用得很顺手的时候,继续深挖原理,往往会给你当头一盆冷水。

Synchronized机制的使用:

1.  带来了不小的时间消耗。加锁、解锁都需要额外操作。
我曾经写过一篇关于python的的锁机制(写得比较简单,有兴趣可以出门左转),大致描述了一下python的锁是怎么获得和释放的。
语言之间也是有共通之处的,虽然Java对我们隐藏了Synchronized的实现逻辑,但道理还是差不多的。
2、带来了不少的系统开销。互斥同步对性能最大的影响是阻塞的实现,因为阻塞涉及到的挂起线程和恢复线程的操作都需要转入内核态中完成
用户态与内核态的切换的性能代价是比较大的,这里就不展开描述了,我自己理解也不是很深刻
 
话虽这么说,Synchronized有如此强大的机制,当然会有它的一席用武之地。在实际项目中,好比如,获取Jedis实例的时候会用到,redis一般也就是存储一下用户的session,
对并发的要求不是非常严格。

我们都知道线程执行任务都是在一定的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的数据库的主从配置 里面进行介绍 

世界上有些东西就是这么美妙,当你一层层剥开它的时候,会忍不住说,哇塞,太棒了吧。

献上杨宗纬的《洋葱》:

如果你愿意一层一层
一层地剥开我的心
你会鼻酸
你会流泪
只要你能
听到我
看到我的全心全意

-EOF-