1. Introduction of ThreadLocal:
Under normal circumstances, the variables we create can be accessed and modified by any thread. If we want the thread to have its own private local variables , then we can use ThreadLocal
classes to achieve this idea.
ThreadLocal
The main solution of the class is to allow each thread to bind its own value . The ThreadLocal class can be compared to a box for storing data, and the private data of each thread can be stored in the box.
Let's take a look at a sample code to understand its use:
import java.text.SimpleDateFormat;
import java.util.Random;
/**
* ThreadLocal示例类
*/
public class ThreadLocalExample {
//SimpleDateFormat不是线程安全的,所以要为每个线程都创建一个本地副本
private static final ThreadLocal<SimpleDateFormat> formarter =
ThreadLocal.withInitial(()->new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String [] args) throws InterruptedException {
ThreadLocalExample example = new ThreadLocalExample();
for (int i = 0;i < 10; ++i){
//创建线程
Thread t = new Thread(()->{
System.out.println("Thread Name="+Thread.currentThread().getName()+" deafult Formatter=" +
formarter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
}catch (Exception e){
e.printStackTrace();
}
formarter.set(new SimpleDateFormat());
System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter=" +
formarter.get().toPattern());
},""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
}
From our results, it can be seen, thread 0 uses
set
the method to modify the value of the variable has changed, but for no other threads in the call
set
before the method,
get
take the values are beginning to initialize the main thread of the default values.
Second, the realization principle of ThreadLocal
Let's first look at a very important static inner class in the ThreadLocal class ThreadLocalMap
:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
.......
This ThreadLocalMap
we can regard it as a similar Map
data structure, its key is an ThreadLocal
object, and its value is an Object
object.
In fact Thread
, there are two ThreadLocalMap
objects in the class , let's take a look:
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
By default, these two variables are null, and they are created only when the current thread calls the set or get method of the ThreadLocal class.
In fact, the ThreadLocal
stored private variables of each thread are stored in the threadLocals
object of the current thread object . When ThreadLocal
the set
method is called , the Thread.currentThread()
current thread t is obtained through first , and then the getMap(t)
method is used to obtain the corresponding to the current thread ThreadLocalMap
, and then Operations on this map:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们来看看上面那个通过线程来获取ThreadLocalMap
的getMap()
方法的实现:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这个方法就是直接获取了线程对象里面的threadLocals
,这个在我们刚刚看Thread
类的时候已经说到了这个对象了。
因此我们可以得出一个结论就是:最终的变量是放在了当前线程的ThreadLocalMap
中,并不是存在ThreadLocal
上,ThreadLocal
可以理解为只是ThreadLocalMap
的封装,传递了变量值。ThrealLocal
类中可以通过Thread.currentThread()
获取到当前线程对象后,直接通过getMap(Thread t)
可以访问到该线程的ThreadLocalMap
对象。
比如我们在同⼀个线程中声明了两个ThreadLocal对象的话,会使⽤Thread内部都是使⽤仅有那个ThreadLocalMap存放数据的,ThreadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调⽤set⽅法设置的值。
三、关于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;
}
}
ThreadLocalMap
中使用的key为ThreadLocal
的弱引用,而value是强引用。所以,如果ThreadLocal
没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样⼀来,ThreadLocalMap
中就会出现key为null的Entry
。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产⽣内存泄露。ThreadLocalMap
实现中已经考虑了这种情况,在调用set()、get()、remove()
方法的时候,会清理掉key为null的记录。使用完ThreadLocal
方法后最好手动调用remove()
方法。