ThreadLocal 使用 示例: 数据路由切换

109 阅读3分钟

ThreadLocal 介绍

ThreadLocal用于多线程环境下每个线程存储和获取线程的局部变量,这些局部变量与线程绑定,线程之间互不影响。 Thread + Local = 线程 + 本地 = 线程本地变量 = 把某个对象放在了线程本地。

应用场景

常用于存放:用户信息

常用于存放:流程信息

管理数据库的Connection

避免一些参数传递

ThreadLocal 特性

  • 每个Thread维护着一个ThreadLocalMap的引用

  • ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

  • 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象

  • 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

  • ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value

ThreadLocal.set()方法源码解读

ThreadLocalMap是ThreadLocal的一个静态内部类,同时在Thread类下有一个成员变量ThreadLocals


public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 把当前ThreadLocal对象作为key,调用set方法的入参对象作为value,放入当前线程的ThreadLocalMap
        map.set(this, value);
    else
        // 通过当前线程和调用set方法的入参对象去构造Map
        createMap(t, value);
}

ThreadLocal.get()方法源码解读

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
	// 通过当前ThreadLocal对象去取Entry
	ThreadLocalMap.Entry e = map.getEntry(this);
	if (e != null) {
	    @SuppressWarnings("unchecked")
	    // 获取Entry的value返回
	    T result = (T)e.value;
	    return result;
	}
    }
    return setInitialValue();
}

结构图

image.png

image.png

ThreadLocal的线程隔离性

线程之间的隔离,不同线程 获取不到线程中set 的值

示例


 

public static void main(String[] args) {
    threadLocalTest1();
}

private static void threadLocalTest1() {
    new Thread(new Runnable() {
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("get------>"+Thread.currentThread().getName());
            //取
            System.out.println("get------>value"+threadLocal.get());
        }
    }).start();


    new Thread(new Runnable() {
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //放
            threadLocal.set(new String("zizhen"));
            System.out.println("set------>"+Thread.currentThread().getName());
            System.out.println("set------>value"+threadLocal.get());
        }
    }).start();
}
 
set------>Thread-1
set------>valuezizhen
get------>Thread-0
get------>valuenull


ThreadLocal 常见问题FQA

  • ThreadLocal是什么?为什么要使用ThreadLocal

  • 一个ThreadLocal的使用案例

  • ThreadLocal的原理

  • 为什么不直接用线程id作为ThreadLocalMap的key

  • 为什么会导致内存泄漏呢?是因为弱引用吗?

  • Key为什么要设计成弱引用呢?强引用不行?

private static void testTo() {
    PageParam param =  new PageParam();
    param.setPageIndex(1000);
    WeakReference<PageParam> weakReference = new WeakReference<PageParam>(param);
    System.out.println(weakReference.get());
    System.gc(); 
    System.gc();  
    System.gc();
    System.out.println(weakReference.get());
}


  • InheritableThreadLocal保证父子线程间的共享数据

  • ThreadLocal的应用场景和使用注意点

ThreadLocal 用法

正确示例

public String set(Integer i) {
    i = (i == null ? 123 : ii);
try {
   Service.isOverPayThreadLocal.set(i);
   //todo 业务方法...
   Service.isOverPayThreadLocal.get();

} finally {
    //切记用完就要移除,不然线程复用,会造成变量值错误
    ConsumptionChargeOffService.isOverPayThreadLocal.remove();
}
   
}

错误示例:

public String get1() {
    try {
        ConsumptionAmountCalculator.test();
        System.out.println("get1----------->" + Thread.currentThread().getName() + "isCarOverPayThreadLocal.get() " + ConsumptionChargeOffService.isCarOverPayThreadLocal.get());

        Thread.sleep(5000000);
    } catch (Exception e
    ) {
    } finally {
        ConsumptionChargeOffService.isCarOverPayThreadLocal.remove();
    }
    return ConsumptionChargeOffService.isCarOverPayThreadLocal.get() + "";

}




原因:在一个线程里面 ThreadLocal  HOLDER 没有改变

private static final ThreadLocal HOLDER = new ThreadLocal();

HOLDER.set(new DynamicRoutingHolderBean(isRead, bizTime, hashKey, specialKey, databaseName));

@Before("within(com.cLCoervice.dao..*) && @annotation(updateImportDate)")

-------切面线程----->:http-nio-12377-exec-8

@Override
public void run() {

-----多线程---------->:thread-runner-0
}

=====目标方法线程======>:http-nio-12377-exec-8

切面调用:同步的:适用同一个主线程

@After("within(com.cLConstaService.dao..*) && @annotation(updateImportDate)")



====目标方法线程=========>:http-nio-12377-exec-3

@Override
public void run() {
----多线程-------->:thread-runner-0

}

----切面线程-------->:http-nio-12377-exec-3




Thread[RMI TCP Connection(3)-127.0.0.1,5,RMI Runtime]


## InheritableThreadLocal

InheritableThreadLocal,即子线程可以访问父线程中的线程本地变量,更严谨的说法是子线程可以访问在创建子线程时父线程当时的本地线程变量,因为其实现原理就是在创建子线程的时候将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。