ThreadLocal源码学习

线程本地变量:线程本地变量通常是一个类中的私有静态的成员变量。我们可以在不同的线程中的调用线程本地变量的getset方法,来获取和设置当前线程中线程本地变量的值。在当前线程改变线程本地变量的值,并不会影响其他线程本地变量的值。

上面一段话有点抽象,先举个例子说一下。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //注释1处,在主线程中设置ThreadLocal保存的变量值
        threadLocal.set("初始名称");
       //注释2处
        System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get());
        new TestThread("线程甲", threadLocal).start();
        new TestThread("线程乙", threadLocal).start();
    }
}

class TestThread extends Thread {

    private ThreadLocal<String> threadLocal;

    public TestThread(String name, ThreadLocal<String> threadLocal) {
        super(name);
        this.threadLocal = threadLocal;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i == 6) {
                //当i==6的时候替换成当前线程名
                threadLocal.set(getName());
            }
            //获取
            System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get() + ",i= " + i);
        }
    }
}

输出如下

main ,初始名称
线程甲 ,null,i= 0
线程甲 ,null,i= 1
线程甲 ,null,i= 2
线程甲 ,null,i= 3
线程甲 ,null,i= 4
线程甲 ,null,i= 5
线程甲 ,线程甲,i= 6
线程甲 ,线程甲,i= 7
线程甲 ,线程甲,i= 8
线程甲 ,线程甲,i= 9
线程乙 ,null,i= 0
线程乙 ,null,i= 1
线程乙 ,null,i= 2
线程乙 ,null,i= 3
线程乙 ,null,i= 4
线程乙 ,null,i= 5
线程乙 ,线程乙,i= 6
线程乙 ,线程乙,i= 7
线程乙 ,线程乙,i= 8
线程乙 ,线程乙,i= 9

在这个例子中,我们创建了一个ThreadLocal变量,内部保存的是一个String类型的变量。

private static ThreadLocal<String> name = new ThreadLocal<>();

在main方法的注释1处,我们在主线程中设置ThreadLocal变量值为初始名称,所以在注释2处输出如下

main ,初始名称

然后我们新建了两个线程,在线程的run方法中首先从0到5threadLocal.get()方法获取的值都是null。
然后从6开始把threadLocal设置为当前线程的名字,然后再调用threadLocal.get()方法输出的就是线程对应的名字了。

源码分析

首先我们看一下ThreadLocal的构造方法

public ThreadLocal() {
    
}

看下ThreadLocal的set(T value)方法相关操作

public void set(T value) {
    Thread t = Thread.currentThread();
    //注释1处
    ThreadLocalMap map = getMap(t);
    //注释2处,注意传入的this是作为key的。
    if (map != null)
        map.set(this, value);
    //注释3处,
    else
        createMap(t, value);
}

在注释1处,首先获取当前线程的ThreadLocalMap对象。ThreadLocal的getMap方法。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Thread类的threadLocals变量是一个ThreadLocal.ThreadLocalMap对象,用来保存与当前线程相关的ThreadLocal变量。

public class Thread implements Runnable {
    //...
    //用来保存与当前线程相关的ThreadLocal变量
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal.ThreadLocalMap是ThreadLocal的一个静态内部类,只适合用来保存线程本地变量。ThreadLocalMap的实体使用弱引用来保存key。注意key是ThreadLocal<?> 。

static class ThreadLocalMap {
    
    //初始容量,必须是2的幂
    private static final int INITIAL_CAPACITY = 16;
    //存储entry的表,根据需要调整大小。表的长度必须是2的幂
    private Entry[] table;
    //...
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        //和ThreadLocal关联的值 
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            //使用弱引用来保存key
            super(k);
            value = v;
        }
    }
}

set(T value)方法的注释3处,如果获取到的map对象为null,则调用createMap方法。

创建一个ThreadLocalMap对象赋值给当前线程的threadLocals变量。同时保存了初始的key和value。注意key是ThreadLocal类型的

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap的两个参数的构造方法

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    //注释1处,看一看 threadLocalHashCode 是怎么计算的。
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //保存初始的key和value
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

注释1处,threadLocalHashCode 是一个final类型的变量。而且是一个原子类型的变量。

private static AtomicInteger nextHashCode =
        new AtomicInteger();
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

set(T value)方法的注释2处,如果获取到的map对象不为null,则调用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法。

ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    //注释1处,获取在map中的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //更新key对应的value
        if (k == key) {
            e.value = value;
            return;
        }
        //替换指定位置上的键值对
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
   //加入到table中
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //是否需要缩减table或者扩容,如果是扩容的话,容量会增加到两倍
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

set(ThreadLocal<?> key, Object value)方法,注释1处,获取在map中的位置,要注意一下这个key,threadLocalHashCode是一个final 类型的变量,在第一次创建ThreadLocal对象的时候初始化,后面就不会变了。

接下来看一下ThreadLocal的get方法

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap对象map
    ThreadLocalMap map = getMap(t);
    if (map != null) {//如果map存在,返回对应的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    //map不存在。调用setInitialValue方法
    return setInitialValue();
}

方法内部判断如果线程的threadLocals不为null,就从中取出对应的值并返回。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

如果map中没有对应的值,返回setInitialValue方法的执行结果。

ThreadLocal的setInitialValue方法

private T setInitialValue() {
    //注释1处,首先调用initialValue方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal的initialValue方法默认返回null

protected T initialValue() {
    return null;
}

我们可以重写ThreadLocal的initialValue方法来提供一个默认值,就像这样。

private static ThreadLocal<String> name = new ThreadLocal<String>(){

    @Override
    protected String initialValue() {
       return "hello world";
    }
};

总结:

  1. 实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的。
  2. 如果想在调用get方法之前不需要调用set方法就想得到一个默认的值的话,可以重写initialValue()方法。

Android 中的Looper 是使用ThreadLocal来保存的。

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

参考链接:

  1. Java并发编程:深入剖析ThreadLocal
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容