JUC并发编程(二)之ThreadLocal线程副本源码分析

ThreadLocal线程副本源码

案例

定义一个静态变量,定义5个Thread线程去访问这个静态变量

public class ThreadLocalTest {
    
    

    private static int num = 0;


    public static void main(String[] args) {
    
    
        Thread [] threads=new Thread[5];
        for(int i=0;i<5;i++){
    
    
            threads[i]=new Thread(()->{
    
    
                num =num+5;
                System.out.println(Thread.currentThread().getName()+"-------"+num);
            });
        }
        for(int j=0;j<5;j++){
    
    
            threads[j].start();
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

执行结果
在这里插入图片描述
如果想让每个线程起始值都为初始值,怎么实现?

通过线程副本ThreadLocal实现

public class ThreadLocalTest {
    
    

    private static int num = 0;

       //线程副本
    private final static ThreadLocal<Integer>  threadLocal=new ThreadLocal<Integer>(){
    
    
        @Override
        protected Integer initialValue() {
    
    
            //初始值为0
            return 0;
        }
    } ;

    public static void main(String[] args) {
    
    
        Thread [] threads=new Thread[5];
        for(int i=0;i<5;i++){
    
    
            threads[i]=new Thread(()->{
    
    
                //num =num+5;
                num=threadLocal.get();
                num=num+5;
                //更新threadLocal在thradlocalmap里存储的值(本质为该线程在entry对应下标的值)
                threadLocal.set(num);
                System.out.println(Thread.currentThread().getName()+"-------"+num);
            });
        }
        for(int j=0;j<5;j++){
    
    
            threads[j].start();
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述
这样每个副本变量存储的值都是单独存在,互不影响
ThreadLocal底层是怎么实现的呢?

源码讲解

进入threadLocal.get()方法

public T get() {
    
    

    //当前线程
    Thread t = Thread.currentThread();

    //底层使用ThreadLocalMap存储数据的
    ThreadLocalMap map = getMap(t);

    if (map != null) {
    
    
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
    
    
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
       //map为空时,进行初始化
    return setInitialValue();
}

源码分析:
Thread t = Thread.currentThread();
获取当前线程,

进入getMap(t)

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

此时发现每个线程都有一个成员变量ThreadLocalMap threadLocals,也就是说每个线程副本存储数据都有各自的ThreadLocalMap进行存储并独立的互不干扰,接下来继续分析源码

进入setInitialValue()源码
//map为空时,进行初始化
return setInitialValue();

private T setInitialValue() {
    
    
    //获取初始值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

进入createMap(t, value);

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

进入ThreadLocalMap构造方法

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);
}

此时我们或许会更清楚,Thread Map里面用Entry数组来存储数据,数组里存放的是firstKey,firstValue形式的值,firstKey是创建的ThreadLocal对象 firstValue是里面存储的值,其实就是(key,value)形式,有这么一行代码,firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)是获取数组的下标,对于同一个ThreadLocal的对象,他们获取的数组下标是一样的,也就是说同一线程副本在ThreadLocalMap存储的位置是相同的,一个线程可以有多个线程副本,同一线程中只是每个线程副本在ThreadLocalMap存储的位置是不相同的

在补充一个方法

threadLocal.set(num);

该方法作用,更新threadLocal在thradlocalmap里存储的值,本质为更改该线程副本在对应的entry数组下标的值,源码如下:

private void set(ThreadLocal<?> key, Object value) {
    
    
    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)]) {
    
    
        ThreadLocal<?> k = e.get();
        //如果存在该线程副本,则直接更改值
        if (k == key) {
    
    
            e.value = value;
            return;
        }
        
        if (k == null) {
    
    
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //如果这是一个新的线程副本,则直接存储
    tab[i] = new Entry(key, value);
    //数值加1
    int sz = ++size;
    //达到一定长度扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

文字和图分析

在这里插入图片描述
这里源码可能不好理解,这里用文字分析:
每个线程都有各自的ThreadLocalMap成员变量,作用就是存储数据的,ThreadLocalMap内用用Entry数组来存储数据,也许大家会很困惑,上图明明是键值对形式的,数组怎么存储的啊?又怎么确保每个线程副本之间的值互补干扰的?
firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)是获取数组的下标,也就说每个线程的threadLocalHashCode是不同的,获取的数组下标不同,并将真正的值作为数组的值,这样就保证了多个线程副本的独立性。也就是说每个线程的Thread Local Map各自独立,每个线程副本之间也互相独立。

猜你喜欢

转载自blog.csdn.net/weixin_42371621/article/details/109551527