ThreadLocal源码理解

本文深入解析了ThreadLocal的工作原理,包括其如何通过ThreadLocalMap在多线程环境中为每个线程提供独立的变量副本,避免了线程间的数据冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导语

ThreadLocal做为Thread中的一个成员变量,可以储存当前线程所对应的值

ThreadLocal的简单例子

public static void main(String[] args) {
    final ThreadLocal<String> tl = new ThreadLocal<>();
    new Thread(){
        public void run(){
            tl.set("new thread");
            String str = tl.get();
            System.out.println("子线程"+str);
        };
    }.start();

    System.out.println("主线程"+tl.get());

    tl.set("main thread");
    System.out.println("主线程2"+tl.get());
}

运行结果:

主线程null
子线程new thread
主线程2main thread

说明:在不同的线程中,ThreadLocal所取的值是不一样的,与相应的线程有紧密的关系。
用法大家可以去别的地方看看,本位重点是讨论实现原理。

读完本篇文章,你应该理解的内容

点这里看大图
这里写图片描述

如果你读完本篇,脑海中会浮现上图,说明你对于ThreadLocal已经理解了。

Thread中用来储存数据的容器

在Thread的成员变量中,是有ThreadLocal相关的引用的

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal在不同线程中的值是区分的,那是因为不同的Thread所引用的ThreadLocal.ThreadLocalMap中的
值是不一样的,其中ThreadLocalMap就是来储存数据的容器,可以用来区分不同的ThreadLocal

ThreadLocal源码的分析

set()方法

//设置value
public void set(T value) {
    //获取当前的线程对象
    Thread t = Thread.currentThread();
    //获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);

    //判断map是否为null
    if (map != null)
        //不为空,当值放入map中
        map.set(this, value);
    else
        //为空,创建并把值放入map中
        createMap(t, value);
}

//根据线程对象获取相应的map
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}


//创建map并且与当前的ThreadLocal建立联系
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

说明:set()中的思路很清晰,就是将数据保存在Thread的容器ThreadLocalMap中

get()方法

//获取数据
public T get() {
    //获取当前的线程对象
    Thread t = Thread.currentThread();
    //获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);

    //map不为空的处理
    if (map != null) {
        //获取相应的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //获取数据,并转换类型
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            //返回结果
            return result;
        }
    }

    //map为空的处理,返回默认的数据null
    return setInitialValue();
}

说明:思路也同样清晰,获取当前Thread中的容器ThreadLocalMap,再取出相应的值

remove()方法

//移除数据
public void remove() {
 //获取当前线程所对应的ThreadLocalMap
 ThreadLocalMap m = getMap(Thread.currentThread());
 //移除相应的数据
 if (m != null)
     m.remove(this);
}

说明:同样很简单,获取当前Thread中的容器ThreadLocalMap,再移除相应的值

小结

从上面的方法分析可以看出,ThreadLocal的实现原理是非常的简单,无非就是与Thread建立了联系,从而可以
区分不同的Thread

成员变量

//ThreadLocal的hashCode,每次创建的ThreadLocal,它的值一般不同
private final int threadLocalHashCode = nextHashCode();


//hashCode,原子操作
private static AtomicInteger nextHashCode =
    new AtomicInteger();


//nextHashCode增长的大小,这个是一个黄金比的数字
//用途:即将整数尽可能均匀地散列到[0, 2^32-1]这个范围当中去,最大限度地减少冲
private static final int HASH_INCREMENT = 0x61c88647;


//获取下一个hashCode
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

说明:上述几个方法中,ThreadLocalMap都是通过ThreadLocal来作为key来区分不同的ThreadLocal
而threadLocalHashCode做为标签,用来区分不同的ThreadLocal,每次初始化ThreadLocal,threadLocalHashCode
都是不一样的,会均匀地散列到[0, 2^32-1]这个范围中

ThreadLocalMap

如果一个线程中有多个ThreadLocal对象,那么是通过ThreadLocalMap来区分不同的ThreadLocal。

成员变量

//初始化的容量,必须是2的指数
private static final int INITIAL_CAPACITY = 16;

//用来储存数据的容器,长度必须是2的指数
private Entry[] table;

//数组的长度
private int size = 0;

//阈值,默认是0
private int threshold; // Default to 0

//Entry继承了弱引用:当长时间没有引用,key会变为Null
//在一些操作下会清空Entry,这样就会节省空间
static class Entry extends WeakReference<ThreadLocal<?>> {
    //真正用来封装数据
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

说明:ThreadLocalMap的数据结构是个数组。每一个槽中是个Entry,里面封装了ThreadLocal的引用和对应的数据。

构造方法

//构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始化数组
    table = new Entry[INITIAL_CAPACITY];
    //获取i:i是均匀地分散在[0,INITIAL_CAPACITY-1]之间的
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //将数据填充到对应的位置
    table[i] = new Entry(firstKey, firstValue);
    //更新size为1
    size = 1;
    //更新阈值:16 * 2 / 3
    setThreshold(INITIAL_CAPACITY);
}

说明:通过ThreadLocal中的threadLocalHashCode来确定数组中的位置,将Value保存到对应的位置上

set()方法

//添加数据
private void set(ThreadLocal<?> key, Object value) {
    //获取数组
    Entry[] tab = table;
    //数组的长度
    int len = tab.length;
    //获取对应的i
    int i = key.threadLocalHashCode & (len-1);

    //循环遍历,找到对应的Entry,更新其中的数据
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {

        //获取当前的k
        ThreadLocal<?> k = e.get();

        //与key匹配的话,更新数据
        if (k == key) {
            e.value = value;
            return;
        }

        //k为null的处理,长时间没有使用,弱引用已经将数据回收了
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //当前的ThreadLocal是第一次添加数据
    //创建Entry,并赋值
    tab[i] = new Entry(key, value);
    //更新size
    int sz = ++size;

    //如果没有移除数据并且更新后的size大于阈值,调用rehash()
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        //会将数组的容器扩充->key.threadLocalHashCode & (len-1)的值也会变化
        //所以会创建一个新的数组,将旧数组中相应的数据赋值到对应的位置
        rehash();
}

get()方法

//根据ThreadLocal获取相应的Entry
private Entry getEntry(ThreadLocal<?> key) {
    //获取i
    int i = key.threadLocalHashCode & (table.length - 1);
    //根据i从数组中获取相应的Entry
    Entry e = table[i];

    //判断Entry中是否储存了数据
    if (e != null && e.get() == key)
        //储存了数据,返回相应的结果
        return e;
    else
        //数据丢失的处理:
        //e为null,直接返回null
        //e不为null,因为长时间未引用,key没了,需要清除下空的引用
        return getEntryAfterMiss(key, i, e);
}

说明:get()方法比较简单,从数组中对应的位置找出数据并返回。

remove()方法

//通过Key来移除Entry
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //找打了对应的Entry,清除数据
            e.clear();
            //清空槽
            expungeStaleEntry(i);
            return;
        }
    }
}

说明:remove()方法同样简单,找出数组中对应的位置,移除数据

关于均匀分布的直观体现

运行下面的Java代码,可以感觉下均匀分布

public class Main {

    public static void main(String[] args) {
        int index = 0;
        int capacity = 16;
        printIndex(capacity, index);

        index = 0;
        capacity = 32;
        printIndex(capacity, index);

        index = 0;
        capacity = 64;
        printIndex(capacity, index);
    }

    private static void printIndex(int capacity,int index){
        for (int i = 0; i < 2 * capacity / 3; i++) {
            System.out.print("  " + ((capacity - 1) & index) + "  ");
            index = next(index);
        }
        System.out.println("换行");
    }

    private static int next(int x) {
        return x + 1640531527;
    }
}

运行结果

  0    7    14    5    12    3    10    1    8    15  换行
  0    7    14    21    28    3    10    17    24    31    6    13    20    27    2    9    16    23    30    5    12  换行
  0    7    14    21    28    35    42    49    56    63    6    13    20    27    34    41    48    55    62    5    12    19    26    33    40    47    54    61    4    11    18    25    32    39    46    53    60    3    10    17    24    31  换行

结语

  1. 通过上面的分析,我们可以看到,ThreadLocal有点像Thread当中的一个属性,是通过ThreadLocalMap来建立连接的,一个Thread可以连接多个ThreadLocal
  2. ThreadLocal最牛X的地方在于:在识别不同的ThreadLocal的同时,运用数学定理将数据均匀地分布在数组中
  3. 一定要理解文章开头给出的图片,这里再给一张不对称的图:

点这里看大图
这里写图片描述
代码可以看不懂,0x61c88647可以忽略,但上图一定要有印象

参考资料
1. HASH_INCREMENT为何确定为0x61c88647?:http://hehaiqian.com/2015/07/25/hash1-0x61c88647/

转载请标明出处http://blog.youkuaiyun.com/qq_26411333/article/details/51689018

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值