详解 BAT 面试必问之 ThreadLocal(源码 + 内存)

image.png
深入理解 ThreadLocal
用途
我们一般用 ThreadLocal 来提供线程局部变量。线程局部变量会在每个 Thread 内拥有一个副本,Thread 只能访问自己的那个副本。文字解释总是晦涩的,我们来看个例子。

public c

lass Test
{
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args)
{
Thread thread1 = new MyThread(“lucy”);
Thread thread2 = new MyThread(“lily”);
thread1.start();thread2.start();
}
private static class MyThread extends Thread
{

MyThread(String name)
{
super(name);
}

@Overridepublic void run()
{
Thread thread = Thread.currentThread();
threadLocal.set("i am " + thread.getName());
try
{
//睡眠两秒,确保线程 lucy 和线程 lily 都调用了 threadLocal 的 set 方法。Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(thread.getName() + " say: " + threadLocal.get());
}
}
}

这个例子非常简单,就是创建了 lucy 和 lily 两个线程。在线程内部,调用 threadLocal 的 set 方法存入一字符串,睡眠 2 秒后输出线程名称和 threadLocal 中的字符串。我们运行这单代码,看一下输出内容。

lucy say: i am lucylily say: i am lily

原理
上面例子很好的解释了 ThreadLocal 的作用,接下来我们分析一下这是如何实现的。

我们定位到 ThreadLocal 的 set 方法。源码中 set 方法被拆分为几个方法,为了表述方便笔者将这几个方法进行了整合。

public void set(T value)
{
//获取当前线程 Thread t = Thread.currentThread();
//获取当前线程的 ThreadLocalMapThreadLocalMap map = t.threadLocals;
if (map != null)
//将数据放入 ThreadLocalMap 中,key 是当前 ThreadLocal 对象,值是我们传入的 value。map.set(this, value);
else
//初始化 ThreadLocalMap,并以当前 ThreadLocal 对象为 Key,value 为值存入 map 中。
t.threadLocals = new ThreadLocalMap(this, value);
}

通过上面这段代码可以看到,ThreadLocal 的 set 方法主要是通过当前线程的 ThreadLocalMap 实现的。ThreadLocalMap 是一个 Map,它的 key 是 ThreadLoacl,value 是 Object。

TreadLocal 的 get 方法的源码我就不贴出来了,大体上与 set 方法类似,就是先获取到当前线程的 ThreadLocalMap,然后以 this 为 key 可以取得 value。

到这里我们基本上明白了 ThreadLocal 的工作原理,我们总结一下

每个 Thread 实例内部都有一个 ThreadLocalMap,ThreadLocalMap 是一种 Map,它的 key 是 ThreadLocal,value 是 Object。

ThreadLocal 的 set 方法其实是往当前线程的 ThreadLocalMap 中存入数据,其 key 是当前 ThreadLocal 对象,value 是 set 方法中传入的值。

使用数据时,以当前 ThreadLocal 为 key,从当前线程的 ThreadLocalMap 中取出数据。

ThreadLocalMap
上面我们介绍了 ThreadLocal 主要是通过线程的 ThreadLocalMap 实现的。

static class ThreadLocalMap
{
private ThreadLocal.ThreadLocalMap.Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {Object value;

Entry(ThreadLocal<?> var1, Object var2)
{
super(var1);this.value = var2;
}
}
}

ThreadLocalMap 是一种 Map,其内部维护着一个 Entry[]。

ThreadLocalMap 其实是就是将 Key 和 Value 包装成 Entry,然后放入 Entry 数组中。我们看一下它的 set 方法。

private void set(ThreadLocal<?> key, Object value)
{
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)
{
//如果已经存在,直接替换 valuee.value = value;return;}

if (k == null)
{
//如果当前位置的 key ThreadLocal 为空,替换 key 和 value。下文 ThreadLocal 内存分析中会提到为什么会有这段代码。replaceStaleEntry(key, value, i);
return;}}

tab[i] = new Entry(key, value);
//该位置没有数据,直接存入。
int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)
//检查是否扩容 rehash();
}

private static int nextIndex(int i, int len)
{
return ((i + 1 < len) ? i + 1 : 0);
}

到这里,如果你了解 HashMap,应该可以看出 ThreadLocalMap 就是一种 HashMap。不过它并没有采用 java.util.HashMap 中数组+链表的方式解决 Hash 冲突,而是采用 index 后移的方式。

我们简单分析一下这段代码:

通过 ThreadLocal 的 threadLocalHashCode 与当前 Map 的长度计算出数组下标 i。

从 i 开始遍历 Entry 数组,这会有三种情况:

Entry 的 key 就是我们要 set 的 ThreadLocal,直接替换 Entry 中的 value。

Entry 的 key 为空,直接替换 key 和 value。

发生了 Hash 冲突,当前位置已经有了数据,查找下一个可用空间。

找到没有数据的位置,将 key 和 value 放入。

检查是否扩容。

我们知道,HashMap 是一种 get、set 都非常高效的集合,它的时间复杂度只有 O(1)。但是如果存在严重的 Hash 冲突,那 HashMap 的效率就会降低很多。我们通过上段代码知道,ThreadLocalMap 是通过 key.threadLocalHashCode & (len-1)计算 Entry 存放 index 的。len 是当前 Entry[]的长度,这没什么好说的。那看来秘密就在 threadLocalHashCode 中了。我们来看一下 threadLocalHashCode 是如何产生的。

public class ThreadLocal
{
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}}
这段代码非常简单。有个全局的计数器 nextHashCode,每有一个 ThreadLocal 产生这个计数器就会加 0x61c88647,然后把当前值赋给 threadLocalHashCode。关于 0x61c88647 这个神奇的常量

【这里想说,因为自己也走了很多弯路过来的,所以才下定决心整理,收集过程虽不易,但想到能帮助到一部分自学或者是想提升java技术、成为Java架构师,提升技术P5-P6-P7-P8 的人,心里也是甜的!有需要的伙伴请点㊦方】↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值