深入剖析ThreadLocal实现原理
来源:互联网 发布:centos解压rar 编辑:程序博客网 时间:2024/05/18 03:25
在2017京东校园招聘笔试题中遇到了描述ThreadLocal的实现原理和内存泄漏的问题,之前看过ThreadLocal的实现原理,但是网上有很多文章将的很乱,其中有很多文章将ThreadLocal与线程同步机制混为一谈,特别注意的是ThreadLocal与线程同步无关,并不是为了解决多线程共享变量问题!
ThreadLocal官网解释:
翻译过来的大概意思就是:ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get
或set
方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static
类型。
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量 副本,提供了保持对象的方法和避免参数传递的复杂性。
ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。
ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。
可以通过上述的几个方法实现ThreadLocal中变量的访问,数据设置,初始化以及删除局部变量,那ThreadLocal内部是如何为每一个线程维护变量副本的呢?
其实在ThreadLocal类中有一个静态内部类ThreadLocalMap
(其类似于Map
),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap
中元素的key
为当前ThreadLocal对象,而value
对应线程的变量副本,每个线程可能存在多个ThreadLocal。
源代码:
/** Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for thecurrent thread, it is first initialized to the value returned by an invocation of the {@link #initialValue} method. @return the current thread's value of this thread-local */public T get() { Thread t = Thread.currentThread();//当前线程 ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//获取对应ThreadLocal的变量值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//若当前线程还未创建ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行创建并返回初始值。}//设置变量的值public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}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的threadlocals,并将第一个值存入到当前map中@param t the current thread@param firstValue value for the initial entry of the map*/void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}//删除当前线程中ThreadLocalMap对应的ThreadLocalpublic void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this);}
上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap
进行操作,下面看一下该类的源代码:
static class ThreadLocalMap { //map中的每个节点Entry,其键key是ThreadLocal并且还是弱引用,这也导致了后续会产生内存泄漏问题的原因。 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } /** * 初始化容量为16,以为对其扩充也必须是2的指数 */ private static final int INITIAL_CAPACITY = 16; /** * 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。 */ private Entry[] table; ///....其他的方法和操作都和map的类似}
总之,为不同线程创建不同的ThreadLocalMap
,用线程本身为区分点,每个线程之间其实没有任何的联系,说是说存放了变量的副本,其实可以理解为为每个线程单独new
了一个对象。
在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?
其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。
它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
关于堆和栈的比较,请参考Java中的堆和栈的区别
既然上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?
使用InheritableThreadLocal
可以实现多个线程访问ThreadLocal的值。
如下,我们在主线程中创建一个InheritableThreadLocal
的实例,然后在子线程中得到这个InheritableThreadLocal
实例设置的值。
private void testInheritableThreadLocal() { final ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("droidyue.com"); Thread t = new Thread() { @Override public void run() { super.run(); Log.i(LOGTAG, "testInheritableThreadLocal =" + threadLocal.get()); } }; t.start();}
上面的代码输出的日志信息为
I/MainActivity( 5046): testInheritableThreadLocal =droidyue.com
使用InheritableThreadLocal
可以将某个线程的ThreadLocal值在其子线程创建时传递过去。因为在线程创建过程中,有相关的处理逻辑。
//Thread.java private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //code goes here if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID();}
上面代码就是在线程创建的时候,复制父线程的inheritableThreadLocals
的数据。
实现单个线程单例以及单个线程上下文信息存储,比如交易id等
实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
承载一些线程相关的数据,避免在方法中来回传递参数
- Java ThreadLocal
- Threadlocals and memory leaks in J2EE
- Java Thread Local – How to use and code sample
- ThreadLocal in Java – Example Program and Tutorial
- 深入剖析ThreadLocal实现原理
- 深入剖析ThreadLocal实现原理以及内存泄漏问题
- 深入剖析ThreadLocal实现原理以及内存泄漏问题
- 深入剖析ThreadLocal实现原理以及内存泄漏问题
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal(2)
- ThreadLocal深入剖析
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- Java ThreadLocal 深入剖析
- 深入剖析 ThreadLocal
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- 深入剖析ThreadLocal
- ThreadLocal实现简单剖析
- 【数据压缩】BMP2YUV实验报告
- 关于Java Web 使用 iText 将数据库中的表的数据 生成 PDF 格式文件(基础版本)
- 浅谈java中extends与implements的区别
- 转:sublime Text3 注册码
- sublime text3 汉化
- 深入剖析ThreadLocal实现原理
- Pycharm Professional Edition激活码
- Python 列表推导式 一些小的古怪写法 if else for in join
- List.Sort()小技巧
- Sublime Text3 必要插件及组件
- python学习笔记
- PHP学习笔记——PHP中其他运算符;
- Android中图片压缩方案详解
- 496. Next Greater Element I的C++解法