欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal实现原理

程序员文章站 2022-07-03 14:59:38
...

ThreadLocal简介

ThreadLocal是java中将非线程安全变为线程安全的一个神器.通过为每一个线程保存一个线程本地变量来保证数据的安全性,通过set()和get()方法来使用. 因为每个线程都拥有变量的副本.不和其他线程变量交互,所以不会出现线程安全问题.所以这里有一个点就是ThreadLocal应用的场景应该是变量的访问是没有依赖关系的,每个线程只和自己的数据副本打交道.

属性

public class ThreadLocal<T> {
    //每个线程有自己的HashCode,并不是根据线程计算的,而是Local分配的
    private final int threadLocalHashCode = nextHashCode();

    //用来计算下一个线程的hash值,threadLocalHashCode就是根据这个Integer计算出来的,每次增加一个一个HASH_INCREMENT的值
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    //hash增量,hash值的增量
    private static final int HASH_INCREMENT = 0x61c88647;
}

主要方法

get()

public T get() {
        Thread t = Thread.currentThread();
        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;
            }
        }
        return setInitialValue();
    }

看代码可以知道首先获取当前线程,然后通过getMap获取到一个ThreadLocalMap.如果当前线程存在map的话,就从map中取出当前线程所需要的值.注意这里getEntry中传入的是this,不是当前线程t.因为map中存储的是键值对是ThreadLocal变量与ThreadLocal变量存储的值的对应.
接下来看getMap方法

getMap()

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

这里就是获得线程自己的ThreadLocalMap.每个线程都有一个ThreadLocal变量.在初始的时候是空的.

// 这是线程中的ThreadLocalMap变量
ThreadLocal.ThreadLocalMap threadLocals = null;

然后上面获取到的.如果获取到的map不是空的,说明已经设置过本地变量.因此就直接获取到此map的键值对.然后返回所要的值.
如果map为空,就通过setInitialValue()方法设置初始值.

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

    //这个方法是用来给子类继承的.在ThreadLoacl中SuppliedThreadLocal内部类继承了此类,用来实现这个方法.设置初始值
 protected T initialValue() {
        return null;
    } 

这个代码也很清晰明了了,如果当前线程有本地map,就直接添加默认值,如果没有,就使用createMap()创建一个

createMap()

创建Map其实就是给当前线程的ThreadLocal赋值.

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

接下来细看一下ThreadLocalMap类的内部实现

ThreadLocalMap

在这里map中的Entry存储的key是一个ThreadLocal变量,值就是设置的值.这里有一点要搞明白.每个线程都有一个ThreadLocalMap的变量.通过这个变量能够找到当前线程存储的每个的ThreadLocal所对应的值.

static class ThreadLocalMap {
    //这里在说一下,这里使用的是弱引用.是为了让在线程将此值释放掉后能够让虚拟机进行回收,当线程本地释放变量的时候,不需要在对map进行主动移除,虚拟机就会自动回收掉无用变量.
    static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    //省略一些基本的map的操作与字段,里面也是使用数组来存储数据.使用线性探测来解决hash冲突.

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

重点看一下getEntry,通过得到当前线程保存的ThreadLocal变量.然后在通过这个值找到对应表中的位置,在这里有一点可能会出现为空或者这个key值不是当前线程保存的本地ThreadLocal的变量的情况.通过在线性探测法遍历整个数组,如果出现为空的话,则直接返回空.

set()

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

这里set方法首先就获取当前线程,在获取一个ThreadLocalMap(),然后设置值

总结

到这里可能会有一些人对这个ThreadLocal还有点懵逼,通过一个完整的例子来看一下.

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

比如说这里面有两个ThreadLocal,上面讲过,线程中存储的是一个ThreadLocalMap,map中存储的是ThreadLocal变量与值的映射.比如这里,线程中的threadLocalMap中会有两对键值对,一个是threadLocal的,另一个是threadLocal1的.当然,拥有这两个键值对的前提是调用了threadLocal.set()方法或者是get方法.如果调用了threadLocal.set(str)方法,那么就会在此线程的map中添加一个映射,就是threadLocal->str的映射.如果在获取的时候,通过获取线程的threadLocalMap,找到此threadLocal对应的值,就是此线程保存的这个threadLocal的值了.

ThreadLocal的实现原理主要就是通过每个线程保存一个ThreadLocalMap的变量,在ThreadLocal中保存的ThreadLoca => value 的键值对映射的一个map,通过找到线程对应的threadlocalmap,然后在从threadlocalmap中找到对应的threadlocal对应的值,就能够获取到对应的变量副本.