Java并发编程之ThreadLocal源码分析

1 一句话概括ThreadLocal

2 ThreadLocal使用场景

3 ThreadLocalMap源码分析

4 ThreadLocal源码分析

5 ThreadLocal总结

1 一句话概括ThreadLocal

  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象的线程创建了一个独立的变量副本。

2 ThreadLocal使用场景

  用一句话总结ThreadLocal真的实在是太苍白无力了!我们通过一个简单的例子入手。比如现在有A和B两台服务器需要通过http请求传递数据,但又希望数据安全性有一定保障,因此发送方A决定用AES算法对传输数据加密后再发送给B。接受方B收到数据后,通过密钥解密数据并进行后续的业务处理。
  对数据进行AES解密,接收方B可选择Java提供的Cipher类来实现。我们在调用Cipher类进行解密时时,需要获取Cipher对象的实例,如下所示:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

  接着我们调用该实例就可以进行数据解密工作。但很不幸的是,Cipher类存在线程安全问题,它无法工作于多线程场景下。简单来说就是单个Cipher实例无法同时解密多条数据。
  那怎么办呢?
有没有什么办法能让每个线程拥有相同的instance实例,且彼此互不干扰呢?这个时候我们可以借助ThreadLocal类来实现特定功能,ThreadLocal能够为每个使用该对象的线程创建独立的变量的副本。这样就满足了我们的需求。

  后续篇幅我们会深入到ThreadLocal的源码层来探讨它的实现机制,在看ThreadLocal类的几个基本方法前,让我们先看一下ThreadLocal中静态类ThreadLocalMap的实现,它对于我们理解ThreadLocal有着举足轻重的作用!

3 ThreadLocalMap源码分析

  ThreadLocalMap是ThreadLocal类中的一个静态类,它拥有一个Entry数组类型的成员变量,名为table。这个Entry是啥?我们来看看。

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }

  Entry类是ThreadLocalMap中的一个静态类,它继承了WeakReference类,同时拥有一个类型为Object的value成员变量。当我们创建Entry对象后,调用Entry.get()方法,获取到的实际上是ThreadLocal对象的弱引用。而这个设计则保证了Entry对象中保存的ThreadLocal弱引用是易被回收的。网上有很多关于ThreadLocal对象是否会引发内存泄漏的文章,这里的内存泄漏通常指的不是entry的key,也就是ThreadLocal的弱引用。而是这里的value对象。实际上ThreadLocalMap自身提供了一套回收无用Entry节点的机制。在后面我们会聊到它的实现。关于ThreadLocal是否会引发内存泄漏,这里暂时不做探讨。

3.1 ThreadLocalMap.set()方法

privatevoidset(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(); }

  ThreadLocalMap类持有一个Entry数组,名为table。当我们调用ThreadLocalMap的set()方法时,其实就是更新table中某个Entry,或往table中插入一个Entry。set()方法其实是根据threadLocal对象的threadLocalHashCode来计算当前Entry节点应落入什么位置。当然存在多个entry落入位置发生冲突的情况,在ThreadLocal中使用了线性探测法来解决冲突。知道了这一点,那set方法的实现思路就很清晰了。就是找一个位置让我放节点嘛!如果已经有现成的了,更新一下value就行。要是找不到,那我就按线性探测法来找落入位置就好了嘛。值得注意的是replaceStaleEntry()方法!当entry节点不为空,而key为null时会调用这个方法。这个方法看名字好像是用来替换过时的Entry节点的?我们来看一下它到底是干嘛的?

private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/d288123c4a13fedc48b5d93f52b5e9ed.html