java并发(3)ThreadLocal的使用及实现原理(实现原理)
2017-08-07 13:46
661 查看
在上一篇文章java并发(2)ThreadLocal的使用及实现原理(使用)中介绍了ThreadLocal的简单使用,这篇文章通过jdk8ThreadLocal的源码分析一下ThreadLocal的实现原理.
首先分析一下ThreadLocal这个类,先从该类的initialValue方法说起:
1.ThreadLocal的initialValue()方法:
该方法默认返回值为null,它被声明为protected,希望它的子类能覆盖该方法从而给本地变量设置初始值,该方法为懒加载,只会在第一次调用get方法的时候才会被执行。
2.ThreadLocal的set(T value)方法
在ThreadLocal调用set方法为该当前线程的本地变量赋值时:
ThreadLocal会先获得当前线程,然后通过调用getMap()方法获得当前线程的ThreadLocalMap,如果该线程已经有了ThreadLoclMap则调用ThreadLocalMap的set(ThreadLocal firstKey, T value)方法将value值存入ThreadLocalMap中(该方法在下文中会详细介绍),如果当前线程还没有ThreadLocalMap实例,则新建一个ThreadLocalMap并将其赋值给当前线程。
其中getMap(Thread t)方法:
createMap(Thread t)方法:
每个线程都有一个自己的ThreadLocalMap,Thread类中的threadLocals变量定义:
关于ThreadLocalMap类,该类是ThreadLocal的一个静态内部类,该类是ThreadLocal非常重要的一个内部类,ThreadLocal对数据的所有操作都发生在这个Map上面 ,当前线程的所有ThreadLocal维护的变量都存放在一个ThreadLocalMap上面,ThreadLocalMap通过使用一张hash表来存放变量,ThreadLocalMap存储的是ThreadLocalMap.Entry对象,ThreadLocalMap.Entry是ThreadLocalMap的一个静态内部类:
ThreadLocalMap持有一个Entry的哈希表table,默认大小为16,加载因子为table长度的2/3,即当table填满程度达到2/3时,table将会扩容。
再来看看ThreadLocalMap的set方法,该方法在前面ThreadLocal类调用set方法时,当拿到ThreadLocalMap后将调用ThreadLocalMap的set()方法将value值存入ThreadLcoalMap中。
ThreadLocalMap的set方法源码如下:
通过这个set方法就已经把本地变量存入到了当前线程的ThreadLocalMap中了,具体步骤已经写在了注释里面。
3.ThreadLocal的get()方法:
ThreadLocal的get方法用来获取当前ThreadLocal所维护的变量的值
其中getEntry(ThreadLocal<?> key) 方法代码如下:
getEntryAfterMiss()方法代码如下:
进入该方法的条件有两个,一个是取出的Entry为空,另一个是取出的Entry的key不是当前的ThreadLocal,可能为空,可能是其他的ThreadLocal,当取出的Entry为空时,该方法直接放回null,对于第二种情况当key是空值时,将会调用expungeStaleEntry()方法,并传入该Entry所在hash表的坐标,ThreadLocal的set、get、remove方法最终都有可能会调用这个expungeStaleEntry()方法,该方法的作用是将key为null的Entry从hash表中清除。对于第二种情况当key不为null并且不是当前ThreadLocal对象时,会取得hash表中的下一个坐标的Entry,并判断取得的Entry的key是否为当前ThreadLocal对象,知道循环完整个hash表如果没有找到,依然返回null。nexIndex方法的作用就是获取下一个坐标,实现如下:
setInitialValue()方法的代码如下
4.ThreadLocal的remove()方法:
remove方法相对来说比较简单,将当前线程当前ThreadLocal对象维护的变量从ThreadLocalMap中移除。
ThreadLocal的remove方法:
ThreadLocalMap的remove方法:
以上就是跟着jdk8的源码深入剖析的ThreadLocal,加上一些个人理解。
总结:总体来说ThreadLocal不能解决共享变量以及协调线程同步的问题,但是ThreadLocal使每个线程都维护着一个自己独有ThreadLocalMap表,将线程私有的资源放入这个Map就不用考虑其他线程的影响,ThreadLocal使用比较经典的场景有:在web应用中,每一个客户端与服务器建立的连接都是一个线程,该线程获得的数据库连接就可以存入ThreadLocalMap中,比如在hibernate框架中,通过SessionFactroy的getSession()取得的Session就是去当前线程的ThreadLocalMap中取得的,如果当前线程的ThreadLocalMap中还没有Session,那么就通过SessionFactroy的openSession()来创建一个兵放入当前线程的ThreadLocalMap中,每个线程都有一个自己的Session,它们之间不共享。在spring中也多处运用了ThreadLocal解决线程安全问题,前段时间看spring源码的时候就是因为不太了解ThreadLocal,所以看到事务管理的时候遇到ThreadLocal的地方看的不明不白,所以这次专门花时间学习了一下ThreadLocal再回头重看spring。
T | get() Returns the value in the current thread's copy of this thread-local variable. 该方法返回一个当前线程所对应的ThreadLocal维护的变量 |
protected T | initialValue() Returns the current thread's "initial value" for this thread-local variable. 该方法在线程第一次调用get方法的时候会被执行,返回一个线程变量的初始值, 该方法被声明为protected,目的是为了让这类去覆盖它从而设置线程变量的初始值, 如果不覆盖,默认返回null。 |
void | remove() Removes the current thread's value for this thread-local variable. 删除当前线程的本地变量,目的是为了减少内存占用,但是当线程销毁时该本地变量也会 被回收,所以该方法不是必须的 |
void | set(T value) Sets the current thread's copy of this thread-local variable to the specified value. 为当前线程的当前ThreadLocal维护的变量设置值 |
static <S> ThreadLocal<S> | withInitial(Supplier<? extends S> supplier) Creates a thread local variable. 创建一个ThreadLocal并返回。 |
1.ThreadLocal的initialValue()方法:
protected T initialValue() { return null; }
该方法默认返回值为null,它被声明为protected,希望它的子类能覆盖该方法从而给本地变量设置初始值,该方法为懒加载,只会在第一次调用get方法的时候才会被执行。
2.ThreadLocal的set(T value)方法
在ThreadLocal调用set方法为该当前线程的本地变量赋值时:
public void set(T value) { Thread t = Thread.currentThread();//获得当前变量线程t ThreadLocalMap map = getMap(t); //获得当前线程的ThreadLocalMap if (map != null) map.set(this, value); //将this作为键,在get的时候同一个ThreadLocal通过get(this)的方式就能直接取到值 else createMap(t, value); //如果当前线程没有ThreadLocalMap,则新建一个 }
ThreadLocal会先获得当前线程,然后通过调用getMap()方法获得当前线程的ThreadLocalMap,如果该线程已经有了ThreadLoclMap则调用ThreadLocalMap的set(ThreadLocal firstKey, T value)方法将value值存入ThreadLocalMap中(该方法在下文中会详细介绍),如果当前线程还没有ThreadLocalMap实例,则新建一个ThreadLocalMap并将其赋值给当前线程。
其中getMap(Thread t)方法:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; //直接返回当前线程的threadLocals,之前提到过Thread类中有一个threadLocals变量,该变量正是ThreadLocalMap类型的 }
createMap(Thread t)方法:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
每个线程都有一个自己的ThreadLocalMap,Thread类中的threadLocals变量定义:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
关于ThreadLocalMap类,该类是ThreadLocal的一个静态内部类,该类是ThreadLocal非常重要的一个内部类,ThreadLocal对数据的所有操作都发生在这个Map上面 ,当前线程的所有ThreadLocal维护的变量都存放在一个ThreadLocalMap上面,ThreadLocalMap通过使用一张hash表来存放变量,ThreadLocalMap存储的是ThreadLocalMap.Entry对象,ThreadLocalMap.Entry是ThreadLocalMap的一个静态内部类:
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持有一个Entry的哈希表table,默认大小为16,加载因子为table长度的2/3,即当table填满程度达到2/3时,table将会扩容。
/** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; //容量初始值 /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; //哈希表 /** * The number of entries in the table. */ private int size = 0; //当前哈希表中实体个数 /** * The next size value at which to resize. */ private int threshold; // Default to 0 加载因子 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; }
再来看看ThreadLocalMap的set方法,该方法在前面ThreadLocal类调用set方法时,当拿到ThreadLocalMap后将调用ThreadLocalMap的set()方法将value值存入ThreadLcoalMap中。
ThreadLocalMap的set方法源码如下:
private void set(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); //通过key的HashCode算出Entry对象应该在table中的坐标 for (Entry e = tab[i]; e != null; //如果计算出的坐标处已经存在 e = tab[i = nextIndex(i, len)]) { //找该坐标的下一个位置,直到找到一个空位 ThreadLocal<?> k = e.get(); //取得计算出的坐标的Entry对象 if (k == key) { //如果该坐标中的Entry的key和要插入的值得key相同,说明只用将之前key对应的值修改成要插入的值就可以返回了 e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); //如果计算出的坐标处是空位,则插入一个新的Entry对象,然后将对象个数size++ int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
通过这个set方法就已经把本地变量存入到了当前线程的ThreadLocalMap中了,具体步骤已经写在了注释里面。
3.ThreadLocal的get()方法:
ThreadLocal的get方法用来获取当前ThreadLocal所维护的变量的值
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //set的时候后将自身作为键,get的时候使用this就可以取得值,敢于该方法,在下面介绍 if (e != null) { //如果取得的Entry不为空,就将Entry的value返回。 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); // 如果当前线程还没有持有ThreadLocalMap实例或者在当前线程的ThreadLocalMap中没有取得Entry实例,就调用初始化方法。 }
其中getEntry(ThreadLocal<?> key) 方法代码如下:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); //将key的hashCode和table的长度进行与运算计算出该key对应的Entry对应在hash表table中的坐标i Entry e = table[i]; //取出Entry if (e != null && e.get() == key) // 如果取出的Entry不为空并且key = Entry中的key,将该Entry返回 return e; else // 否则调用getEntryAfterMiss方法 return getEntryAfterMiss(key, i, e); }
getEntryAfterMiss()方法代码如下:
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { //参数key传入的当前ThreadLocal对象 Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) // 当找到当前ThreadLocal对象对应的Entry时返回结果 return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); //取得hash表的下一个坐标 e = tab[i]; } return null; }
进入该方法的条件有两个,一个是取出的Entry为空,另一个是取出的Entry的key不是当前的ThreadLocal,可能为空,可能是其他的ThreadLocal,当取出的Entry为空时,该方法直接放回null,对于第二种情况当key是空值时,将会调用expungeStaleEntry()方法,并传入该Entry所在hash表的坐标,ThreadLocal的set、get、remove方法最终都有可能会调用这个expungeStaleEntry()方法,该方法的作用是将key为null的Entry从hash表中清除。对于第二种情况当key不为null并且不是当前ThreadLocal对象时,会取得hash表中的下一个坐标的Entry,并判断取得的Entry的key是否为当前ThreadLocal对象,知道循环完整个hash表如果没有找到,依然返回null。nexIndex方法的作用就是获取下一个坐标,实现如下:
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
setInitialValue()方法的代码如下
private T setInitialValue() { T value = initialValue(); //上文中第一点提到的初始化方法,给要维护的变量赋初始值,可以看到,只有在调用get方法的时候,并且当前线程还未持有ThreadLocalMap实例的时候才会调用该方法,如果在get之前调用了set方法,那么当前线程就会持有一个ThreadLocalMap实例,辣么initialValue()方法将永远不会被调用。 Thread t = Thread.currentThread(); //下面几行代码类似于ThreadLocal的set方法,参看上文第二点 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
4.ThreadLocal的remove()方法:
remove方法相对来说比较简单,将当前线程当前ThreadLocal对象维护的变量从ThreadLocalMap中移除。
ThreadLocal的remove方法:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap的remove方法:
private void remove(ThreadLocal<?> key) { 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)]) { if (e.get() == key) { e.clear(); //该方法是Entry的超级父类java.lang.ref.Reference<T>的一个方法,该方法作用是将Entry的key设为null。 expungeStaleEntry(i); //将key为null的Entry从hash表中清除,从而完成remove功能。 return; } } }
public void clear() { this.referent = null; }
以上就是跟着jdk8的源码深入剖析的ThreadLocal,加上一些个人理解。
总结:总体来说ThreadLocal不能解决共享变量以及协调线程同步的问题,但是ThreadLocal使每个线程都维护着一个自己独有ThreadLocalMap表,将线程私有的资源放入这个Map就不用考虑其他线程的影响,ThreadLocal使用比较经典的场景有:在web应用中,每一个客户端与服务器建立的连接都是一个线程,该线程获得的数据库连接就可以存入ThreadLocalMap中,比如在hibernate框架中,通过SessionFactroy的getSession()取得的Session就是去当前线程的ThreadLocalMap中取得的,如果当前线程的ThreadLocalMap中还没有Session,那么就通过SessionFactroy的openSession()来创建一个兵放入当前线程的ThreadLocalMap中,每个线程都有一个自己的Session,它们之间不共享。在spring中也多处运用了ThreadLocal解决线程安全问题,前段时间看spring源码的时候就是因为不太了解ThreadLocal,所以看到事务管理的时候遇到ThreadLocal的地方看的不明不白,所以这次专门花时间学习了一下ThreadLocal再回头重看spring。
相关文章推荐
- java并发(2)ThreadLocal的使用及实现原理(使用)
- java并发编程学习: ThreadLocal使用及原理
- Java高并发编程三--volatile使用及其实现原理
- java并发编程学习: 阻塞队列 使用 及 实现原理
- Java并发学习之ThreadLocal使用及原理介绍
- ThreadLocal的实现原理,及使用实例,解决spring,hibernate非web项目下的懒加载 no session or session was closed(2)!
- java中使用线程实现Timer(定时器)原理和源码
- java中使用线程实现Timer(定时器)原理和源码
- java中使用线程实现Timer(定时器)原理和源码
- java中使用公钥加密私钥解密原理实现license控制
- Java并发包中的同步队列SynchronousQueue实现原理
- java中使用公钥加密私钥解密原理实现license控制
- java中使用公钥加密私钥解密原理实现license控制
- Android学习笔记---java实现多线程下载器,30_多线程下载原理介绍和使用
- Java枚举(用Java普通类模拟枚举的实现原理及JDK枚举API使用示例)
- java中使用线程实现Timer(定时器)原理和源码
- java中使用线程实现Timer(定时器)原理和源码
- java中使用线程实现Timer(定时器)原理和源码
- Java枚举(用Java普通类模拟枚举的实现原理及JDK枚举API使用示例)
- java中使用线程实现Timer(定时器)原理和源码