多个线程访问同一个共享资源很容易出现线程安全问题,为了保证线程安全,一般会使用同步机制,同步机制一般的方式是加锁,这需要对锁有一定的了解,显然加重了使用者的负担,那么有没有一种方式,可以做到,当创建一个变量之后,每个线程访问的是自己的变量呢?ThreadLocal刚好能解决该问题的。
ThreadLocal提供了线程的本地变量。每个线程在使用这个变量的时候,都会在当前线程中创建这个变量的本地副本。
ThreadLocal的作用:它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
ThreadLocal的简单使用:
public class ThreadLocalTest {
ThreadLocal<String> strVal= new ThreadLocal<>();
ThreadLocal<Integer> intVal= new ThreadLocal<>();
public void print(String str){
System.out.println(str+" "+strVal.get());
System.out.println(str+" "+intVal.get());
strVal.remove();
intVal.remove();
}
@Test
public void Test(){
Thread threadOne=new Thread(()->{
strVal.set("我到站了");
intVal.set(20);
print("线程1");
System.out.println("线程1本地变量被移除之后:"+strVal.get());
System.out.println("线程1本地变量被移除之后:"+intVal.get());
});
threadOne.start();
}
}
运行结果:
源码之下无密码。ThreadLocal相关类的关键属性和方法的类图如下:
这里有三个关键的地方:
1、ThreadLocal的set()、get()方法
2、Thread的属性threadLocals
3、ThreadLocalMap
ThreadLocalMap是一个定制化的HashMap,是ThreadLocal的静态内部类。
Thread中有个threadLocals的属性,这个属性的类型是ThreadLocalMap。
Thread的相关源码如下
public class Thread implements Runnable {
//....省略相关源码....
ThreadLocal.ThreadLocalMap threadLocals = null;
//....省略相关源码....
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//.....省略相关源码....
)
ThreadLocal的set()方法源码
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的属性ThreadLocals
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()方法的简单讲解
set()方法第一步就是获取当前线程。然后获取当前线程的属性ThreadLocals(类型为ThreadLocalMap)。最后就是把ThreadLocal的实例对象作为key,把value存入ThreadLocals属性中,及存入当前线程的ThreadLocalMap的中。
所以在当前线程中使用ThreadLocal对象调用set()方法,实际上就是把ThreadLocal对象当成key,把value存入当前线程的ThreadLocals属性中。
看到这里也就大概明白get()方法底层是如何实现的了,就是把ThreadLocal对象当成key,从当前线程的ThreadLocals属性中取值。
ThreadLocal的get()方法源码
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current 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();
//获取当前线程的threadLocal属性
ThreadLocalMap map = getMap(t);
//跟上面猜测的一样,如果当前线程的threadLocals属性不为空,从threadLocals中获取值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get()方法的简单讲解
如果当前线程的threadLocals不为空,则把ThreadLocal对象当成key,从当前线程的ThreadLocals属性中获取值。在get()方法最后,返回了setInitialValue()。
setInitialValue()源码
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;
}
protected T initialValue() {
return null;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
setInitialValue()方法简单说明。
如果当前线程的threadLocals不为空,则设置当前线程的本地变量值为null,否则调用createMap()方法创造一个本地变量为null的ThreadLocalMap对象赋值给threadLocals。
ThreadLocal的remove()方法源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
removo()简单说明
如果当前线程的threadLocals不为空,则删除当前线程中指定ThreadLocal实例的本地变量。
最常见的ThreadLocal使用场景是用来解决数据库连接、Session管理
总结:
每一个线程都会有一个threadLocals的属性,该属性的类型为ThreadLocalMap,ThreadLocalMap是一个定制化的HashMap。其中的key值为我们定义的ThreadLocal对象,及上述案例中的strVal,intVal。value则为我们使用set()方法设置的值。
初始化时,当前线程中的threadLocals属性的值为null,当通过ThreadLocal对象调用get()和set()方法时,都会创建ThreadLocalMap对象对threadLocals进行初始化。
每个线程的本地变量存放在自己线程threadLocals变量当中,如果当前线程不消亡,那么本地变量就会一直存在,
可能会造成内存溢出,因此使用完成之后,调用ThreadLocal的remove()方法,删除对应线程的threadLocals中的本地变量
参考:《java并发编程之美》