线程安全处理之Threadlocal

881 阅读2分钟

今天项目中遇到一个问题:利用SecurityContextHolder获取用户登录信息的时候一直报空指针异常。网上查询原来这是一个线程级别的全局变量,只能在主线程上访问。

在Spring中,对例如如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态。

概述

Threadlocal是Thread的局部变量,它为每个使用该变量的线程提供独立的变量副本。每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,最终目的是实现线程之间的数据隔离(有个关键字叫Synchronized是实现线程之间的数据隔离)。

ThreadLocal提供的四个方法作用如下:

  • initialValue() //返回该线程局部变量的初始值 ,在get操作没有对应的值时,调用此方法
  • get() //返回当前线程所对应的线程变量副本
  • set(Object value)//设置当前线程的线程变量副本
  • remove() //将当前线程变量副本的值删除

原理

介绍原理之前需要了解另外两个类:

  • Thread //用于操作线程 ,内部有ThreadLocalThreadLocalMap属性
  • 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线程无影响
    }
}