zoukankan      html  css  js  c++  java
  • Java 线程之ThreadLocal的应用场景及原理

    1.ThreadLocal是什么呢?

      ThreadLocal与并发问题相关,每个ThreadLocal能够存放一个线程级别的变量,而它本身又能被多个线程共享使用,并且绝对的线程安全(数据隔离),它本身是为线程安全和某些特定场景的问题而设计的。说到这是不是有点迷惑,反正笔者刚了解的ThreadLocal的时候是相当的迷惑,既然这个变量被多线程共享,怎么又说到数据隔离了呢,如果数据隔离的话,不要让线程共享变量不就行了。其实,所说的共享只是共享一个变量引用而已,并不是共享里面的数据,里面的数据是分开存储的和访问的,所以实现了数据的隔离。比如你哥哥结婚了,你们还住在一个房子里,但是你哥的媳妇始终是你哥的,永远不可能是你的。房子就像一个ThreadLocal变量,你和哥哥是两个线程,你嫂子是数据,并且只属于你哥哥。这下该清楚了吧。

      很多人只是将它作为一种传参的工具,方法调用的时候,最初可能没有想那么多,有多少个参数就传递多少个参数,当发现某个方法需要增加一个参数时,我们总不能直接添加参数吧,如果你非要改,那你改个试试。针对这个问题,可以通过ThreadLocal的方式很方便的进行参数传递。注意使用的时候明白它的出口和入口是可控的,否则很容易造成内存泄漏。

    2.应用场景

      为了说明ThreadLocal的应用场景。我们来看一个框架的样例。Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。而且Spring也将DataSource进行了包装,重写了当中的getConnection()方法,或者说该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。
      为什么要放在ThreadLocal里面呢?由于Spring在AOP后并不能向应用程序传递參数。应用程序的每一个业务代码是事先定义好的,Spring并不会要求在业务代码的入口參数中必须编写Connection的入口參数。此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,不论什么时候都能拿到,此时Spring很清楚什么时候回收这个连接,也就是很清楚什么时候从ThreadLocal中删除这个元素。

      从Spring事务管理器的设计上能够看出。Spring利用ThreadLocal得到了一个非常完美的设计思路,同一时候它在设计时也十分清楚ThreadLocal中元素应该在什么时候删除。由此,我们简单地觉得ThreadLocal尽量使用在一个全局的设计上。而不是一种打补丁的间接方法。

    3.使用方式

    public class ThreadLocalTest {
        public static void main(String[] args) {
            A a = new A();
            for (int i = 0; i < 3; i++) {
                final Integer num = i;       
                final String name = "sun"+i;
                new Thread(){
                    public void run(){
                        try {
                            a.setId(num);
                            a.setname(name);
                            Thread.sleep(5000);
                            a.display();
                        } catch (InterruptedException e) {
    
                        } finally {
                            a.clear();
                        }
    
                    }
    
                }.start();
            }
        }
    
    }
    
    class A{
    
        public static ThreadLocal<String> name = new ThreadLocal<>();
    
        public static ThreadLocal<Integer> id = new ThreadLocal<>();
    
        public void setname(String name){
            A.name.set(name);
        }
        public void setId(Integer id){
            A.id.set(id);
        }
    
        public void display(){
            System.out.println("name: "+A.name.get()+"	"+"id: "+A.id.get());
        }
    
        public void clear() {
            A.name.remove();
            A.id.remove();
        }
    }
    //输出的结果
    name: sun2 id: 2 name: sun0 id: 0 name: sun1 id: 1

    通过输出结果可以看出,通过同一个ThreadLocal得到的变量是线程私有的。

    4.源码分析(先放这占个位置,以后再更新,最近看源码都要看吐了。。。。)

      接下来,分析一下ThreadLocal变量中最常用的方法set(),get(),remove()的源码,来看看它是如何做到线程私有的呢。

      a)set()方法:

      

      方法很简单,首先获取了当前的线程,然后根据当前的线程获取了它的一个threadLocals变量,这个变量是ThreadLocalMap类型的。

       最后,如果map不存在则创建一个。

       b) get()方法。同样的套路,如果不存在数据则返回一个初始值。

      

       c)remove()方法。详细的分析以后再补充。

     5.注意事项  

        我们往ThreadLocal存的数据都会保存在线程的一个map中,如果线程不用了,那么存放的数据自然跟着销毁了。可是,像jvm中的线程是复用的,这就会出现一个问题,数据没用了,但是依然保存在内存中清理不掉,数据的生命周期变得不可预测,极端的情况与jvm的生命周期一致,这会造成严重的内存泄漏,进而导致内存溢出的情况。这是使用ThreadLocal的一个大坑。所以在使用过程中,一定要把ThreadLocal变量安排的明明白白,及时清理数据。

    6.ThreadLocal与Synchronized的区别:

      ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。Synchronized利用锁机制,以时间换空间的方式,ThreadLocal则以空间换时间,用于线程间的数据隔离。

  • 相关阅读:
    Ubuntu16.04下Django项目的部署
    Ubuntu16.04 下python2 | python3
    请求头请求体对应表
    Django项目开发-小技巧
    前端验证后端验证码问题
    Ugly Number
    移动0元素
    图片(画布上的图片)上传总结
    从矩阵中查找一个数
    搜索框(附带事件函数)
  • 原文地址:https://www.cnblogs.com/amazing-eight/p/13269879.html
Copyright © 2011-2022 走看看