ThreadLocal浅析

2022-02-14 15:18:44 浏览数 (2)

深入了解ThreadLocal的话,可以看下 深入细节ThreadLocalMap

一、ThreadLocal是什么

ThreadLocal是一个工具,可以操作当前线程的ThreadLocalMap数据,ThreadLocalMap数据不能跨线程,为解决多线程程序的并发问题提供了一种新的思路。

二、ThreadLocal使用

有时我们不想当前线程中的值被其他线程修改。下面示例可能不能准确地表达思想,误喷啊。。

代码语言:javascript复制
public int TestValue=0;
public static void main(String[] args) {
    Test test=new Test();
    System.out.println("当前线程 值" test.TestValue);
    test.updateValue();
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("当前线程 值" test.TestValue);
}
代码语言:javascript复制
public void updateValue(){
    Thread T1=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新线程 修改前 值" TestValue);
            TestValue=1;
            System.out.println("新线程 修改后 值:" TestValue);
        }
    });
    T1.start();
}

我们可以得到以下输出

我们可以看到新线程修改TestValue值后,当前线程受影响了。

如果我们用ThreadLocal变量呢,这样的行为会不会影响我们程序使用?我们来看下

代码语言:javascript复制
public ThreadLocal<Integer> TestLocalValue2=new ThreadLocal<>();
public static void main(String[] args) {
    Test test=new Test();
    test.TestLocalValue2.set(0);
    System.out.println("当前线程 值" test.TestLocalValue2.get());
    test.updateThreadLocalValue();
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("当前线程 值" test.TestLocalValue2.get());
}

public void updateThreadLocalValue(){
    Thread T1=new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("新线程 修改前 值" TestLocalValue2.get());
            TestLocalValue2.set(1);
            System.out.println("新线程 修改后 值" TestLocalValue2.get());
        }
    });
    T1.start();
}

我们可以得到以下输出

我们可以发现,

1.新线程修改ThreadLocal变量数据,对当前线程不受影响

2.新线程拿不到当前线程ThreadLocal变量数据,新线程修改ThreadLocal变量前,拿到的数据是null。

三、源码粗略说明

我们来看下get方法里面做了什么事

代码语言:javascript复制
public T get() {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t);//获取该线程中ThreadLocalMap数据
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//获取当前线程ThreadLocal为key的Entry数据
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//创建ThreadLocalMap同时返回null
}
代码语言:javascript复制
ThreadLocalMap getMap(Thread t) { //t 是当前线程
    return t.threadLocals; //默认初始值是null
}
代码语言:javascript复制
private T setInitialValue() {
    T value = initialValue(); // 默认初始值null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t); 
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); //创建 ThreadLocalMap
    return value;  //返回null
}
代码语言:javascript复制
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

代码语言:javascript复制
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);
}

get方法大致流程是

1.获取当前线程,找到当前线程ThreadLocalMap数据

2.如果ThreadLocalMap不为null,且有数据,则返回数据。

3.如果ThreadLocalMap不为null,但是没有数据,则把null添加到ThreadLocalMap中,同时返回null。

4.如果ThreadLocalMap为null,则创建ThreadLocalMap,同时返回null。

我们再来看下set方法

代码语言:javascript复制
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

大致流程是

1.获取当前线程,找到当前线程ThreadLocalMap数据。

2.如果ThreadLocalMap不为null,则把数据添加到ThreadLocalMap中。

3.如果ThreadLocalMap为null,则创建ThreadLocalMap。

我们可以看到ThreadLocal里面核心是ThreadLocalMap。ThreadLocal数据的操作底层是由ThreadLocalMap完成的。我们来看看ThreadLocalMap结构

代码语言:javascript复制

private static final int INITIAL_CAPACITY = 16;// 初始容量 —— 必须是2的冥

private Entry[] table;//存放数据的table

private int size = 0;// 数组里面entrys的个数,可以用于判断table当前使用量是否超过负因子

private int threshold; // Default to 0  进行扩容的阈值,表使用量大于它的时候进行扩容

Entry存储结构

代码语言:javascript复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁,导致ThreadLocal对象无法被回收的问题。

四、简单总结

整个流程不复杂。里面主要涉及到三个主体,Thread、TheadLocalMap、TheadLocal。我们画个关系图来看下

TheadLocal中的get、set方法实际是调用当前线程Thread中TheadLocalMap的get、set方法,对Entry数组进行各种操作。同时TheadLocal是Entry中的key。

以上是对TheadLocal简单了解,对TheadLocal深入了解,可以看下 深入细节ThreadLocalMap

0 人点赞