ThreadLocal类及其常用方法解析

  • 背景
    ThreadLocal提供了一种解决并发问题的新思路,以往为了多线程访问全局变量(共享变量)不出现安全问题,我们可以通过同步监视器来实现,而ThreadLocal是把共享变量变为线程独自拥有的变量,这样就不存在共享变量了,自然也不会有并发共享变量的问题。

  • 栗子
    先整一个栗子,方便下面对源码的理解

import java.util.Random;

public class Demo2 {
    private static ThreadLocal data = new ThreadLocal();

    static class InnerRunnable implements Runnable {

        @Override
        public void run() {
            int num = new Random().nextInt();
            data.set(num);
            System.out.println(Thread.currentThread().getName() + " " + num);
            System.out.println(Thread.currentThread().getName() + " " + data.get());
        }
    }

    public static void main(String[] args) {
        InnerRunnable innerRunnable = new InnerRunnable();
        Thread thread = new Thread(innerRunnable);
        Thread thread1 = new Thread(innerRunnable);
        thread.start();
        thread1.start();

    }
}
  • 常用方法
    一、set(T value)
    结合上面的栗子,我们来分析一下存值的过程:

1、首先我们是需要创建一个全局ThreadLocal变量

ThreadLocal data = new ThreadLocal():

2、调用set方法进行存值操作

int num = new Random().nextInt();
data.set(num);

3、让我们来看一下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方法后:
3.1、获取当前线程

Thread t = Thread.currentThread();

3.2、获取线程私有的ThreadLocalMap

ThreadLocalMap map = getMap(t);

为什么说线程私有?
来看getMap(Thread t)具体做了什么

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

getMap(Thread t)会返回线程t的threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

可以看到threadLocals是一个ThreadLocalMap,说明每个线程都有自己的一个ThreadLocalMap。
3.4、获取map后
3.4.1 如果map == null

createMap(t, value);

3.4.1.1、createMap做了什么呢?
map为空当然要先创建map对象:

t.threadLocals = new ThreadLocalMap(this, firstValue);

ThreadLocalMap(this, firstValue);构造函数做了什么呢?

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

①、实例化Entry[] 数组,初始长度为INITIAL_CAPACITY(16)

private Entry[] table;
table = new Entry[INITIAL_CAPACITY];

②、获取存value的索引位置

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

我的理解是一个取模动作
③、将value和firstKey存到指定位置,key是ThreadLocal的虚引用
④、设置存储个数size,因为是初始化所以是1
⑤、设置阈值,当到达初始化长度的三分之二的时候,会对数组进行扩容

private void setThreshold(int len) {
        threshold = len * 2 / 3;
 }

3.4.2 若map不为空

map.set(this, 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);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();

如果不为空,首先访问Entry数组,通过ThreadLocal的哈希值取模获得存储位置,获得该位置存储的Entry e = tab[i],判断该位置是否已经存有一个Entry,有的话,判断key是否相等,是的话更新value,如果该位置key是null,重新设置?,如果该位置没有一个Entry,直接存入,this指当前的ThreadLocal;
二、get()
ThreadLocal类及其常用方法解析_第1张图片

  • 总结
    每个线程都有自己的map,map底层是个Entry数组,Entry里存的是共享变量ThreadLocal和共享变量的值value,ThreadLocal为键值,通过其哈希值取模得到存储位置
    ThreadLocal类及其常用方法解析_第2张图片

你可能感兴趣的:(并发编程)