ThreadLocal原理解析

作者: 酱爆大头菜 | 来源:发表于2019-10-31 14:50 被阅读0次

    在分析ThreadLocal之前,首先我们提出三个问题,后续会围绕这三个问题解析ThreadLocal的原理。

    • 什么是ThreadLocal?
    • ThreadLocal怎么用?
    • ThreadLocal的原理是什么?为什么能保证每个线程的数据都不受其他线程干扰?

    1.什么是ThreadLocal?
    • 首先抛个定义,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。
    • 翻译成能听懂的话就是可以通过ThreadLocal保证同一线程的某些数据不被其他线程干扰,影响。
    2.ThreadLocal怎么用?

    ThreadLocal的使用及其简单,常用的方法只有两个,get(),set();

       ThreadLocal<String> threadLocal = new ThreadLocal<>();
       threadLocal.set(TAG);
       threadLocal.get();
    

    举个使用ThreadLocal的真实场景

    •        2.1.我们正常的项目中有很多复杂的逻辑,首先我们会分割不同的业务,并单独开启多个线程去执行对应的业务。
    •        2.2. 每个业务需要打印本业务的全量日志。正常可通过Log.d(TAG,Message)来打印;每条的日志的TAG都需要一条条的设置。
    •        2.3. 其实该场景可以使用ThreadLocal.set(TAG),在Log工具类中使用Log.d(ThreadLoacl.get(),Message)即可,省去了频繁设置TAG的操作。
    3. ThreadLocal的原理是什么?为什么能保证每个线程的数据都不受其他线程干扰?
    • 原理可以理解为每一个线程类内都有一个ThreadLocalMap类型的局部变量,该变量中设有一个ThreadLocalMap.Entry[]数组,数组中的每一个ThreadLocalMap.Entry对象中存的是k/v(ThreadLocal/Value)键值对,而我们存储的数据就在这个键值对中。
    • 正常的使用中主要分为3步
        1. new ThreadLocal<>();
        1. threadLocal.set(Object);
        1. threadLocal.get();
      • 下文主要从这三步进行展开分析。

    1. ThreadLocal<String> threadlocal = new ThreadLocal<>();

    • 该操作调用了ThreadLocal的构造函数,除了将ThreadLocal内部属性threadlocalHashcode自增了0x61c88647外,并没有做其他的操作。

    2. 当调用ThreadLocal.set(Object),该操作中主要涉及了4个步骤。

    1. Thread t = Thread.currentThread();//获取当前线程
    
    2. ThreadLocalMap map = ThreadLocal.getMap(t);//获取当前线程中的ThreadLocalMap对象。
    
    3. map!=null 则调用 map.set(ThreadLocal,Value);
           获取ThreadLocalMap内部Entry[]数组,通过ThreadLocal.threadLocalHashCode & (len-1)计算将要存放的位置索引。
           循环向后判断冲突,如果Entry[i]存在数据,且key和将要存储的数据相同,则替换value,如果key为null则认为当前数据是脏数据,启动环形清理。
           没有冲突则 new ThreadLocalMap.Entry(key, value)添加到Entry[i]并启动清理,如果清理不成功并且Entry.len>len*0.75则启动扩容rehash(),扩大到原来大小的2倍。
    
    4. map == null 则调用 ThreadLocal.createMap(Thread,Value);
           Thread.threadLocalMap = new ThreadLocalMap(ThreadLocal,Value);
           ThreadLocalMap.Entry[] entry = new ThreadLocalMap.Entry[16]//初始化Entry[]数组。
           通过 i = ThreadLocal.threadLocalHashCode & (len - 1)计算在ThreadLocalMap.Entry[]数组中的存储索引。
           ThreadLocalMap.Entry[i] = new ThreadLocalMap.Entry(k,v);
    
    

    3. 当调用ThreadLocal.get(),该操作中也主要涉及了4个步骤

    1. Thread t = Thread.currentThread();
    
    2. ThreadLocalMap map = t.threadLocals;//获取当前线程中的ThreadLocalMap对象。
    
    3. map != null map.getEntry(ThreadLocal);
          通过 i = ThreadLocal.threadLocalHashCode & (len - 1)计算当前ThreadLocal数据存储的位置。
          Entry e = ThreadLocalMap.Entry[i] ,e!=null且e.get()和当前ThreadLocal key相等则返回 e,否则循环后续的数据.
          如有key相同的则返回,如有脏数据(key==null的数据为脏数据)则清理。
    
    4. map == null ThreadLocal.setInitialValue()
          t.threadLocals = new ThreadLocalMap(ThreadLocal, null);
          ThreadLocalMap.Entry[] entry = new ThreadLocalMap.Entry[16]//初始化Entry[]数组。
          通过 i = ThreadLocal.threadLocalHashCode & (len - 1)计算在ThreadLocalMap.Entry[]数组中的存储索引。
          ThreadLocalMap.Entry[i] = new ThreadLocalMap.Entry(k,null);
    

    总结

      1. 每一个Thread 都维护了一个类型为ThreadLocalMap名叫threadLocals的对象,每次get获取的时,均通过Thread.currentThread()获取了当前线程的ThreadLocalMap,因此做到了不同线程之间互不干扰。
      1. 当调用ThreadLocal.set()方法时,会通过当前线程获取到线程中的ThreadLocalMap对象。
        然后获取到ThreadLocalMap.Entry[]数组,最后通过hash计算出数组中的存放位置,然后new ThreadLocalMap.Entry(ThreadLocal<?> k, Object v)存放在数组内,该索引下有重复key则替换,key==null则启动环形清理,没数据则插入。
      1. 当调用ThreadLocal.get()方法时,ThreadLocal会通过线程获取当前线程中的ThreadLocalMap对象.
        然后获取到ThreadLocalMap.Entry[]数组,并通过hash计算出数组中的存放位置.
        由ThreadLocalMap.Entry[i]获取到数据,判断key相同则返回,key == null则清理。
        如果获取ThreadLocalMap == null,则启动初始化,通过ThreadLocalMap创建长度为16的ThreadLocalMap.Entry[],并存value 为 null的一条数据到Entry[]数组下。

    相关文章

      网友评论

        本文标题:ThreadLocal原理解析

        本文链接:https://www.haomeiwen.com/subject/smozvctx.html