**
一:ThreadLocal的简要介绍及使用
**
Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
ThreadLocal的常见用法:
- 存储单个线程上下文信息。比如存储id等;
- 使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;
- 减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的整个一次处理过程中所有的信息,从而方便debug。由于需要在工程各处随时取用,可放入ThreadLocal。(如果想要当前线程的子线程共享父线程的变量,可以使用InheritableThreadLocal)
如何创建ThreadLocal变量
以下代码展示了如何创建一个ThreadLocal变量:
private ThreadLocal my = new ThreadLocal();
我们可以看到,通过这段代码实例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。
如何访问ThreadLocal变量
一旦创建了一个ThreadLocal变量,你可以通过如下代码设置某个需要保存的值:
my.set("A thread local value”);
可以通过下面方法读取保存在ThreadLocal变量中的值:
String val = (String) my.get();
get()方法返回一个Object对象,set()对象需要传入一个Object类型的参数。
为ThreadLocal指定泛型类型
我们可以创建一个指定泛型类型的ThreadLocal对象,这样我们就不需要每次对使用get()方法返回的值作强制类型转换了。下面展示了指定泛型类型的ThreadLocal例子:
private ThreadLocal my = new ThreadLocal<String>();
现在我们只能往ThreadLocal对象中存入String类型的值了。
并且我们从ThreadLocal中获取值的时候也不需要强制类型转换了。
如何初始化ThreadLocal变量的值
由于在ThreadLocal对象中设置的值只能被设置这个值的线程访问到,线程无法在ThreadLocal对象上使用set()方法保存一个初始值,并且这个初始值能被所有线程访问到。
但是我们可以通过创建一个ThreadLocal的子类并且重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。这样就可以所有线程共享这个初始化值。代码如下:
ThreadLocal<String> my = new ThreadLocal<String>() {@Overrideprotected String initialValue() {return "init val.";}};
一个完整的ThreadLocal例子
public class Demo03 {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable() {private ThreadLocal<String> threadLocal = new ThreadLocal<String>() {@Overrideprotected String initialValue() {return "init val.";}};@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+threadLocal.get());String val = new Random().nextDouble()+"";System.out.println(Thread.currentThread().getName()+val);threadLocal.set(val);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+threadLocal.get());}};for(int i=1;i<10;i++) {new Thread(runnable).start();}}
}
关于InheritableThreadLocal
InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每个线程拥有它自己的值,与ThreadLocal不同的是,InheritableThreadLocal允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。
【注:所有子线程都会继承父线程保存的ThreadLocal值】
**
二:ThreadLocal的原理
**
ThreadLocal的源码分析
1.每个Thread对象内部维护一个ThreadLocalMap这样的一个<key=ThreadLocal对象,value=要存储的value> Map。
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
2.当我们调用ThreadLocal对象的get()方法时,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,则取出ThreadLocal的value,否则进行初始化并返回
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();public static void main(String[] args) {threadLocal.get();threadLocal.set("set值");}public T get() {Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap 引用:return t.threadLocals;ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {//当前ThreadLocalMap不为空,直接取值并返回@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//ThreadLocalMap为空,则进行初始化,并返回return setInitialValue();}
3.当我们调用set方法时,直接将键值对放入Map即可(Map为空则先创建)
public void set(T value) {Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap 引用ThreadLocalMap map = getMap(t);//map不为空,直接设值,为空则创建mapif (map != null)map.set(this, value);elsecreateMap(t, value);
}
4.需要注意的是,ThreadLocalMap的Entry,维护了ThreadLocal的弱引用,当调用ThreadLocal的remove()方法时,会清除当前线程的当前ThreadLocal的Entry,垃圾回收就会回收Entry的对应的value对象。
为防止内存泄漏,ThreadLocal需要手动去释放资源
//ThreadLocal.ThreadLocalMap.Entry
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}public void testThreadLocal() {ThreadLocal<String> threadLocal = new ThreadLocal<String>();//当ThreadLocal不再使用后,为防止内存泄漏,建议手动清空//方式1:清除当前ThreadLocal在ThreadLocalMap中的键值对(Entry)threadLocal.remove();//方式2:将ThreadLocal引用置为nullthreadLocal = null;
}
拓展:
ThreadLocalMap的Hash冲突怎么解决?
Hash冲突怎么解决
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
/*** Increment i modulo len.*/
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);
}/*** Decrement i modulo len.*/
private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);
}
另一个写的较好的博文:
https://blog.csdn.net/qq_23315711/article/details/78642171