**
一: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>() {
@Override
protected 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>() {
@Override
protected String initialValue() {
return "init val.";
}
};
@Override
public 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不为空,直接设值,为空则创建map
if (map != null)
map.set(this, value);
else
createMap(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引用置为null
threadLocal = 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.youkuaiyun.com/qq_23315711/article/details/78642171