今天项目中遇到一个问题:利用SecurityContextHolder
获取用户登录信息的时候一直报空指针异常。网上查询原来这是一个线程级别的全局变量,只能在主线程上访问。
在Spring中,对例如如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态。
概述
Threadlocal
是Thread的局部变量,它为每个使用该变量的线程提供独立的变量副本。每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,最终目的是实现线程之间的数据隔离(有个关键字叫Synchronized
是实现线程之间的数据隔离)。
ThreadLocal
提供的四个方法作用如下:
- initialValue() //返回该线程局部变量的初始值 ,在get操作没有对应的值时,调用此方法
- get() //返回当前线程所对应的线程变量副本
- set(Object value)//设置当前线程的线程变量副本
- remove() //将当前线程变量副本的值删除
原理
介绍原理之前需要了解另外两个类:
- Thread //用于操作线程 ,内部有
ThreadLocal
和ThreadLocalMap
属性- ThreadLocalMap //
ThreadLocal
内部类,用来存储数据,存储了以threadLocal为key,需要隔离的数据为value。
Thread内有个threadLocals,该属性用来保存该线程本地变量。ThreadLocal进行set()和get()操作时都要首先获取当前线程,然后获取线程内的threadLocals,如果threadLocals存在,则以threadlocal为key调取vlaue或set值。这样每个线程都有自己的数据,就做到了不同线程间数据的隔离,保证了数据安全。
public
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
案例
public class ThreadLocalTest {
private static String s1;
private static ThreadLocal<String> s2 = new ThreadLocal<>();
public static void main(String[] args) {
s1 = "test1";
threadLabel.set("test1");
//开启一个新线程,改变s1,s2的值
Thread thread = new Thread() {
@Override
public void run() {
s1= "test2";
s2.set("test2");
}
};
thread.start();
System.out.println("s1值为" +s1 );//获取到s1变化为为test2
System.out.println("s2值为" +s2.get());//获取到的s2仍然为为test1,在另外线程赋值对main线程无影响
}
}