入坑JAVA多线程并发(八)详解ThreadLocal使用和原理

  ThreadLocal是一个用于存储多线程变量的类,它可以把线程与设置的值对应起来,因为它为变量在每个线程都创建了一个副本。访问的时候每个线程只能访问到自己的副本变量。

实例

看如下代码:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        ThreadLocal threadLocal = new ThreadLocal();
        Thread1 thread1 = new Thread1("线程A",threadLocal);
        Thread1 thread2 = new Thread1("线程B",threadLocal);
        Thread1 thread3 = new Thread1("线程C",threadLocal);
        Thread1 thread4 = new Thread1("线程D",threadLocal);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

class Thread1 extends Thread{

    ThreadLocal threadLocal;

    public Thread1(String name,ThreadLocal threadLocal){
        super(name);
        this.threadLocal = threadLocal;
    }

    @Override
    public void run() {
        try {
            for(int i = 0;i< 10;i++){
                threadLocal.set(getName()+i);
                System.out.println(getName()+": "+threadLocal.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

输出为:

线程B: 线程B0
线程D: 线程D0
线程A: 线程A0
线程C: 线程C0
线程C: 线程C1
线程A: 线程A1
线程B: 线程B1
线程D: 线程D1
线程D: 线程D2
线程C: 线程C2
线程A: 线程A2
线程B: 线程B2
线程B: 线程B3
线程D: 线程D3
线程A: 线程A3
线程C: 线程C3
线程C: 线程C4
线程A: 线程A4
线程B: 线程B4
线程D: 线程D4

可以看到效果和预期是一样的,每个线程都只是获取自己设置的值。

原理

每个线程内部存储着一个ThreadLocalMap 实例的,ThreadLocalMap 实例存储着一个或者多个ThreadLocal实例,获取的时候就可以根据当前线程获得对应设置的值
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);
    }

    //获取Map进行设值,如果没有就新建一个map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;           //Thread的一个属性,初始值为NUll;
     }

    //这个是Thread的代码,每个Thread存储一个map的
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //新建一个Map,ThreadLocalMap是内部类
    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);
   }

可以看出这里是把每个线程的threadLocals 设置为一个ThreadLocalMap 的实例,ThreadLocalMap 存储的是key是threadLocals实例,value是对应的值
如下图:
入坑JAVA多线程并发(八)详解ThreadLocal使用和原理_第1张图片
每个线程都有一个threadLocals ,指向对应的ThreadLocalMap 实例,ThreadLocalMap 是一个map结构,ThreadLocalMap 里面每个Entry存储着一个对应的value值,Entry实例的key是ThreadLocal实例,value是当前线程在这个ThreadLocal设置的value值;如果对map结果不熟,可参考Java从入门到放弃(十)集合框架之HashMap源码(1);

ThreadLocalMap的Hash,ThreadLocalMap的key值取的是ThreadLocal实例,这里hash和hashmap有一些不同:

    private final int threadLocalHashCode = nextHashCode();  //hash值

    private static AtomicInteger nextHashCode =
        new AtomicInteger();                   //原子操作类
    }
    private static final int HASH_INCREMENT = 0x61c88647;    //每次hash间隔

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);     //获取下一个hash值
    }

这里是把hash值按照HASH_INCREMENT 的间隔相加。这样子可以保证不会出现相同hash的情况,避免Entry数组的Entry太过于集中,让Entry均匀分布。(这个数字可以让计算出来的索引比较均匀分布)
get方法

    public T get() {
        Thread t = Thread.currentThread();  //获取当前线程
        ThreadLocalMap map = getMap(t);    //获取map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  //获取对应Entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        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;
    }
    //初始化
    protected T initialValue() {
        return null;
    }

这里就是获取当前线程,取得线程的map,根据Threadlocal为key找到对应的value值。如果没有设置就初始化并且设置对应值为null;

remove方法

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
    }

这个也很简单,就是获取对应的map然后删除。

你可能感兴趣的:(入坑JAVA多线程)