一、ThreadLocal定义
ThreadLocal叫做线程局部变量,也就是说ThreadLocal中的变量是属于当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,因此这种方式可以解决线程访问的安全问题。
二、ThreadLocal如何解决线程安全问题
1.ThreadLocal和Synchonized都用于解决多线程并发访问。
2.相比于Synchronized,ThreadLocal通过实现线程间的数据隔离来保证多线程并发安全问题,而Synchronized则通过实现锁机制,使变量或代码块在同一时刻该只能被一个线程访问,这样就隔离了多个线程对数据的数据共享,以此来保证线程安全。
总结来说:ThreadLocal是作为当前线程属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadLocal,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。
三、ThreadLocal原理
原理我们可以结合源码分析:
ThreadLocal 中主要涉及三个方法,set、get、remove。
1.set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化,因此每个线程都有独立的threadLocalMap。
2.get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return 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;
}
ThreadLocal get首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则获取对应的变量值,若为空,则会设置一个初始化的值。
3.remove方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
remove方法,直接将ThrealLocal 对应的值从当前Thread中的ThreadLocalMap中删除。为什么要删除,这涉及到内存泄露的问题。
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,因为value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何解决呢?
解决办法太简单了,在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。
三、ThreadLocal使用场景
1.场景一:在重入方法中替代参数的显式传递方式
假如在我们的业务方法中需要调用其他方法,同时其他方法都需要用到同一个对象时,可以使用ThreadLocal替代参数的传递或者static静态全局变量。这是因为使用参数传递造成代码的耦合度高,那么使用静态全局变量在多线程环境下不安全。当该对象用ThreadLocal包装过后,就可以保证在该线程中独此一份,同时和其他线程隔离。
例如在Spring的@Transaction事务声明的注解中就使用ThreadLocal保存了当前的Connection对象,避免在本次调用的不同方法中使用不同的Connection对象。
2.场景二:在Spring中的实例分析DateTimeContextHolder类
,里面用了ThreadLocal存储时间的上下文
public final class DateTimeContextHolder {
private static final ThreadLocal<DateTimeContext> dateTimeContextHolder =
new NamedThreadLocal<>("DateTimeContext");
private DateTimeContextHolder() {
}
/**
* Reset the DateTimeContext for the current thread.
*/
public static void resetDateTimeContext() {
dateTimeContextHolder.remove();
}
/**
* Associate the given DateTimeContext with the current thread.
* @param dateTimeContext the current DateTimeContext,
* or {@code null} to reset the thread-bound context
*/
public static void setDateTimeContext(@Nullable DateTimeContext dateTimeContext) {
if (dateTimeContext == null) {
resetDateTimeContext();
}
else {
dateTimeContextHolder.set(dateTimeContext);
}
}
/**
* Return the DateTimeContext associated with the current thread, if any.
* @return the current DateTimeContext, or {@code null} if none
*/
@Nullable
public static DateTimeContext getDateTimeContext() {
return dateTimeContextHolder.get();
}
3.存储用户的session
例子:
private static final ThreadLocal<Session> threadSession = new ThreadLocal();
public static Session getSession() {
Session session = threadSession.get();
try {
if (session == null) {
session = getSessionFactory().openSession();
threadSession.set(session );
}
} catch (Exception ex) {
throw new InfrastructureException(ex);
}
return session ;
这样就实现了session的存储。
四、ThreadLocal简单使用
public class ThreadLocalDemo {
private static ThreadLocal<String> local = new ThreadLocal<String>();
static void print(String s) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + local .get());
//清除本地内存中的本地变量
local .remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
ThreadLocalDemo.local.set("localOne");
print("One");
//打印本地变量
System.out.println("after remove : " + local .get());
}
},"One").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
ThreadLocaDemo.localVar.set("localTwo");
print("Two");
System.out.println("after remove : " + local .get());
}
},"Two").start();
}
}
结果
One :localOne
after remove : null
Two :localTwo
after remove : null
大家看完应该会有所理解。