1.使用demo

package com.xiayu.demo;

import lombok.extern.slf4j.Slf4j;

import java.util.Locale;

@Slf4j
public class ThreadLocalDemo {

    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 5;
        }
    }; //cout的初始值是0

    private static ThreadLocal<Locale> language = new ThreadLocal<Locale>(){
        @Override
        protected Locale initialValue() {
            return Locale.CHINA;
        }
    };//languag的初始值为

    public static void main(String[] args) {

        Thread[] threads = new Thread[5];

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {

                //language
                Locale lan = language.get();
                log.info(Thread.currentThread().getName() + ".language:" + lan);

                language.set(Locale.ENGLISH); //每个线程可针对具体场景设置该线程需要的值

                log.info(Thread.currentThread().getName() + ".language:" + language.get());

                //--------------------------------------------------------------------------------
                int num = count.get(); //每个线程获取count的一个拷贝
                num += 5;
                count.set(num); //进行加5,后再保存到线程里面
                //... 进行了别的业务操作

                count.get();//业务中需要获取到count的值
                log.info(Thread.currentThread().getName() + ":" + count.get()); //再获取到count的值

                count.remove();
                language.remove();
            }, "thread-" + i);
            threads[i].start();
        }
    }
}

运行结果
image.png

2.源码

分析数据结构:一个线程可能有多个ThreadLocal变量,存储线程的ThreadLocal变量使用的是嵌套map,
a. set()方法

------------------------------------------------
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value); //如果线程ThreadLocalMap不为null,就进行set
    else
        createMap(t, value); //如果为null,就进行new 操作,t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap new操作
-------------------------------
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
-----------------------------------------------

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

    Entry[] tab = table; // table为存储线程局部变量 private Entry[] table;
    int len = tab.length; //数组的长度
    int i = key.threadLocalHashCode & (len-1); //取余(len-1)得到索引index

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) { //如果e为null,nextIndex方法会找到下一个索引,即使到了最后一个,下一个索引是0  return ((i + 1 < len) ? i + 1 : 0); 目的是找到一个初始化的Entry 
         //如果线程局部变量已经被赋值了,e = tab[i]不会为null,就会走k == key的逻辑,如果线程局部变量没有被赋值,接下来就是找到一个已经new 过的entry,然后走 k == null的逻辑
        ThreadLocal<?> k = e.get();

        if (k == key) { //已经被赋值过后
            e.value = value;
            return;
        }

        if (k == null) { //找到的新创建的entry
            replaceStaleEntry(key, value, i);//new 过了,但k为null,设置该tab[i] = value;
            return;
        }
    }

    //执行到这里说明,0-len全null,然后就new 一个Entry对象
    tab[i] = new Entry(key, value); 
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

b. get()方法

public T get() {
    Thread t = Thread.currentThread(); //首先获取当前线程
    ThreadLocalMap map = getMap(t); //ThreadLocalMap是存储每个线程的局部变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); //this为当前线程局部变量
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value; //获取局部变量
            return result;
        }
    }
    return setInitialValue();//如果ThreadLocalMap值为null的话就返回默认值
}
-------------------------

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

ThreadLocalMap 为ThreadLocal的静态内部类,其实现是使用数组实现的,private Entry[] table; // 线程局部变量使用其hash值取余数组的长度作为key,使用数组索引表示该局部变量

c. remove()方法

首先获取到当前线程,然后获取当前线程的ThreadLocalMap,然后删除
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) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

3.总结

线程局部变量真正的存储在Thread类的ThreadLocalMap中,其是基于数组实现的,局部变量类的hash code取余数组长度作为key,value就是局部变量类的值,操作Thread局部变量是在ThreadLocal类中完成的。通过Thread t = Thread.currentThread();获取当前线程。

4.具体使用场景

可以在一次请求中将请求的国家语言,数据库要连接的名称,表名都可以设置在线程局部变量中,然后在具体业务逻辑要使用到这些值时直接通过ThreadLocal类获取到这些值。


你若安好便是晴天
82 声望10 粉丝