Threadlocal底层是通过threadlocalMap进行存储键值 每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。 ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。 谁设置谁负责移除。
总结一下,个人认为使用ThreadLocal的场景最好满足两个条件:
-
一是该对象不需要在多线程之间共享;
-
二是该对象需要在线程内被传递
应用:在配置多数据源时,动态设置数据源
第一步,在项目启动时加载配置中设置的多种数据源,以自定义的名字或其他标志作为key。
第二步,继承框架中的 AbstractRoutingDataSource类实现提供key的方法,框架源码会在每次访问数据库时都会调用这个方法获得数据源的key,再通过key获得具体数据源。
第三步,通过AOP和注解拦截访问数据库的方法,在访问前设置该方法调用的key变量。
那么我们主要关注第二步怎么在访问数据源前获得key,即实现提供key的方法。
如果将key作为静态变量那可能引起并发问题,当同时访问数据库时,一个线程刚刚设置的key可能被另一个线程修改了,导致最终访问的数据源不正确。那么怎么样才能保证key能不被其它线程修改呢,即不能控制并发也不能每个线程都实例化DynamicDataSource来设置该线程的key,这时候ThreadLocal就能起到很好的作用,保护该线程私有的变量。
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
-
Synchronized是通过线程等待,牺牲时间来解决访问冲突
-
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
public class ThreadLocalTest { static class MyThread extends Thread { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); @Override public void run() { super.run(); for (int i = 0; i < 3; i++) { threadLocal.set(i); System.out.println(getName() + " threadLocal.get() = " + threadLocal.get()); } } } public static void main(String[] args) { MyThread myThreadA = new MyThread(); myThreadA.setName("ThreadA"); MyThread myThreadB = new MyThread(); myThreadB.setName("ThreadB"); myThreadA.start(); myThreadB.start(); } }
ThreadA threadLocal.get() = 0 ThreadB threadLocal.get() = 0 ThreadA threadLocal.get() = 1 ThreadA threadLocal.get() = 2 ThreadB threadLocal.get() = 1 ThreadB threadLocal.get() = 2 虽然两个线程都在向threadLocal对象中set()数据值,但每个线程都还是能取出自己设置的数据,确实可以达到隔离线程变量的效果。