ThreadLocal使用方法&实现原理
个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈
Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
创建ThreadLocal变量
[code]private ThreadLocal myThreadLocal = new ThreadLocal();
我们可以看到,通过这段代码实例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。
访问ThreadLocal变量
一旦创建了一个ThreadLocal变量,你可以通过如下代码设置某个需要保存的值:
[code]myThreadLocal.set("A thread local value”);
可以通过下面方法读取保存在ThreadLocal变量中的值:
[code]String threadLocalValue = (String) myThreadLocal.get();
为ThreadLocal指定泛型类型
我们可以创建一个指定泛型类型的ThreadLocal对象,这样我们就不需要每次对使用get()方法返回的值作强制类型转换了。下面展示了指定泛型类型的ThreadLocal例子:
[code]private ThreadLocal myThreadLocal = new ThreadLocal<String>();
现在我们只能往ThreadLocal对象中存入String类型的值了。并且我们从ThreadLocal中获取值的时候也不需要强制类型转换了。
初始化ThreadLocal变量的值
由于在ThreadLocal对象中设置的值只能被设置这个值的线程访问到,线程无法在ThreadLocal对象上使用set()方法保存一个初始值,并且这个初始值能被所有线程访问到。
但是我们可以通过创建一个ThreadLocal的子类并且重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。就像下面代码展示的那样:
[code]private ThreadLocal myThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "This is the initial value"; } };
多个线程局部变量 ThreadLocal 的使用
不同的线程局部变量,比如说声明了n个(n>=2)这样的线程局部变量threadlocal,那么在Thread中的threadlocals中是怎么存储的呢?threadlocalmap中是怎么操作的?
在ThreadLocal的set函数中,可以看到,其中的map.set(this, value);把当前的threadlocal传入到map中作为key,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。
ThreadLocal实现原理
[code]public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
在set方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。
线程隔离的秘密,就在于ThreadLocalMap这个类。
[code]/** * 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. */ 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. */ static class Entry extends WeakReference<ThreadLocal> { /** * The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal key) { int i = key.threadLocalHashCode & (table.length - 1); ThreadLocal.ThreadLocalMap.Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } }
ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
[code]ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代码已经说的非常直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:
[code]/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
那么接下来再看一下ThreadLocal类中的get()方法,代码是这么说的:
[code]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(); } 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; }
这两个方法的代码告诉我们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。
设置到这些线程中的隔离变量,会不会导致内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题。
最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许也只能创建那么一个,那就没有别的办法了,老老实实的使用同步机制来访问吧。
附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读/书籍书单大全:
(点击右侧 即可打开个人博客内有干货):技术干货小栈
=====>>①【Java大牛带你入门到进阶之路】<<====
=====>>②【算法数据结构+acm大牛带你入门到进阶之路】<<===
=====>>③【数据库大牛带你入门到进阶之路】<<=====
=====>>④【Web前端大牛带你入门到进阶之路】<<====
=====>>⑤【机器学习和python大牛带你入门到进阶之路】<<====
=====>>⑥【架构师大牛带你入门到进阶之路】<<=====
=====>>⑦【C++大牛带你入门到进阶之路】<<====
=====>>⑧【ios大牛带你入门到进阶之路】<<====
=====>>⑨【Web安全大牛带你入门到进阶之路】<<=====
=====>>⑩【Linux和操作系统大牛带你入门到进阶之路】<<=====天下没有不劳而获的果实,望各位年轻的朋友,想学技术的朋友,在决心扎入技术道路的路上披荆斩棘,把书弄懂了,再去敲代码,把原理弄懂了,再去实践,将会带给你的人生,你的工作,你的未来一个美梦。
- 点赞
- 收藏
- 分享
- 文章举报
- mdev的使用方法和原理以及实现U盘或SD卡的自动挂载
- ftrace:event的实现原理和使用方法
- SAMBA 的实现原理以及使用方法
- java并发(3)ThreadLocal的使用及实现原理(实现原理)
- HTTP使用BASIC认证的原理及实现方法
- HTTP使用BASIC认证的原理及实现方法(还有NTLM方法,比较复杂)
- HTTP使用BASIC认证的原理及实现方法
- Hadoop的实现原理及基本使用方法
- python双端队列原理、实现与使用方法分析
- mdev的使用方法和原理以及实现U盘或SD卡的自动挂载
- HTTP使用BASIC认证的原理及实现方法
- GPIO-KEY的实现原理及使用方法
- HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法
- 浅谈HTTP使用BASIC认证的原理及实现方法
- react-redux高阶组件connect方法使用介绍以及实现原理
- 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
- ThreadLocal的实现原理,及使用实例,解决spring,hibernate非web项目下的懒加载 no session or session was closed(1)!
- unix/linux下线程私有数据实现原理及使用方法
- 搜狗/QQ云输入法使用方法与实现原理
- HTTP使用BASIC认证的原理及实现方法