ThreadLocal如何实现?

这是我上周的帖子的后续文章,在那篇文章中我解释了ThreadLocal用法动机 。 从帖子中我们可以回想起,如果您希望每个线程都有一个独立初始化的变量副本,则ThreadLocal确实是一个很酷的概念。 现在,好奇的人可能已经开始问“我如何在Java中实现这样的概念”?

否则,您可能会觉得这不是一个有趣的话题–毕竟,您在这里所需的只是一张地图 ,不是吗? 在处理ThreadLocal <T>时 ,将解决方案实现为HashMap <Thread,T>并以Thread.currentThread()作为键似乎是明智的。 其实不是那么简单。 因此,如果您有五分钟的时间,请忍受我,我将指导您完成一个美丽的设计概念。

简单的HashMap解决方案的第一个明显问题是线程安全性。 由于HashMap并不是为支持并发使用而构建的,因此我们无法在多线程环境中安全地使用该实现。 幸运的是,我们不需要花很多时间来解决问题-ConcurrentHashMap <Thread,T>看起来像是天作之合。 检索的完全并发性和可调整的预期更新并发性正是我们首先需要的。

现在,如果您将基于ConcurrentHashMap的解决方案应用于JDK源代码中的ThreadLocal实现,则会带来两个严重的问题。

  • 首先,在Map结构中将线程作为键。 由于映射永远不会被垃圾回收,因此最终您将永远保持对Thread的引用,从而阻止了该线程成为GCd。 不情愿的是,您在设计中造成了巨大的内存泄漏。
  • 第二个问题可能需要更长的时间才能浮出水面,但是即使在幕后进行了巧妙的分段以减少锁争用的机会, ConcurrentHashMap仍然承担同步开销。 在同步需求仍然存在的情况下,您仍然拥有一个结构,可能成为瓶颈的根源。

但是,让我们首先开始解决最大的问题。 如果我们的引用是指向相关线程的最后一个引用,则我们的数据结构需要允许对线程进行垃圾回收。 再次,第一个可能的解决方案是盯着我们–而不是通常引用对象,为什么不使用WeakReferences ? 因此,实现现在看起来类似于以下内容:

Collections.synchronizedMap(new WeakHashMap<Thread, T>())

现在,我们已经解决了泄漏问题–如果除我们之外没有人引用Thread ,则可以将其定型并进行垃圾回收。 但是我们仍然没有解决并发问题。 现在,解决方案实际上是关于跳出思路思考的样本。 到目前为止,我们已经将ThreadLocal变量视为映射到变量的Threads 。 但是,如果我们颠倒了思路,而是设想了一个解决方案,将ThreadLocal对象映射到每个Thread中的值,该怎么办? 如果每个线程都存储该映射,并且ThreadLocal只是该映射的接口,则可以避免同步问题。 更好的是,我们还避免了GC带来的问题!

确实,当我们打开ThreadLocalThread类的源代码时 ,我们看到这正是在JDK中实际实现该解决方案的方式:

public class Thread implements Runnable {
	ThreadLocal.ThreadLocalMap threadLocals = null;
	// cut for brevity
}
public class ThreadLocal<T> {
	static class ThreadLocalMap {
		// cut for brevity
	}

	ThreadLocalMap getMap(Thread t) {
		return t.threadLocals;
	}

	public T get() {
		Thread t = Thread.currentThread();
		ThreadLocalMap map = getMap(t);
		if (map != null) {
			ThreadLocalMap.Entry e = map.getEntry(this);
			if (e != null)
				return (T) e.value;
		}
		return setInitialValue();
	}

	private T setInitialValue() {
		T value = initialValue();
		Thread t = Thread.currentThread();
		ThreadLocalMap map = getMap(t);
		if (map != null)
			map.set(this, value);
		else
			createMap(t, value);
		return value;
	}
	// cut for brevity
}

因此,这里有它。 Thread类保留对ThreadLocal.ThreadLocalMap实例的引用,该实例是使用对键的弱引用构建的。 以相反的方式构建结构,因为我们的ThreadLocal只能访问当前线程中的值,所以我们完全避免了线程争用问题。 另外,当Thread完成工作时,映射可以进行垃圾回收,因此我们也避免了内存泄漏的问题。

希望您对设计有所了解,因为它确实是解决复杂问题的理想解决方案。 我确实认为阅读源代码是学习新概念的理想方式。 而且,如果您是Java开发人员,那么与阅读集成到JDK的Joshua BlochDoug Lea源代码相比,还有什么比这更好的获取知识的地方了?

参考: 如何实现ThreadLocal? 由我们的JCG合作伙伴 Nikita Salnikov TarnovskiPlumbr Blog博客上获得。

翻译自: https://www.javacodegeeks.com/2013/11/how-is-threadlocal-implemented.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值