多线程 之 TreadLocal

ThreadLocal 是一个线程内部的存储类,可以在指定线程内存储数据,数据存储后,只有在该线程能得到存储数据。

该类提供线程局部变量。 这些变量与它们的正常对应物的不同之处在于,访问其中的每个线程(通过其getset方法)具有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户ID或事务ID)。

ThreadLocal是Thread内的局部变量,可通过为每个线程提供独立的变量副本的方式,解决变量并发访问的冲突问题。

ThreadLocal的应用场景:

在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。

使用 ThreadLocal 情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。

分析ThreadLocal源码:

分析存储类,最主要的就是分析 set() 和 get() 方法:

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // ThradLocalMap 是实际存储的内部结构
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// 为当前线程创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看出,每个线程都有一个ThreadLocalMap对象,若没有便创建一个,ThreadLocalMap存储的便是ThreadLocal对象和value的映射。

接下来分析TreadLocal的内部类ThreadMap:

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal对象确定了一个数组下标,而这个下标就是value存储的对应位置。

ThreadMap源码:

// ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 创建一个Entry数组
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
// Entry 可以理解为 ThreadLocal对象和value的映射关系
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
         super(k);
         value = v;
    }
}

可以看出,在实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            // 计算数组下标
            int i = key.threadLocalHashCode & (len-1);

            // 遍历tab,如果已经存在就更新值
            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中直接创建映射
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

总结:

  • 对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
  • 对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。

ThreadLocal 的 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();
    }

理解了之前的 set() 和 ThreadLocal,这部分就很简单了,get() 就是先根据ThreadLocal对象取出Entry 映射关系,再得到value值

remove() 方法:将局部变量ThreadLocal 的值从 ThreadMap中移除

public void remove() {
    // 获取当前线程的TreadMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

总结

ThreadLocal是变量具有线程隔离的效果,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

ThreadLocal的简单使用:

public class ThreadLocalDemo {

    static ThreadLocal<String> local = new ThreadLocal<>();
    static CountDownLatch latch = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程"+Thread.currentThread().getName());
                local.set("value1");
                System.out.println(local.get());
                latch.countDown();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程"+Thread.currentThread().getName());
                local.set("value2");
                System.out.println(local.get());
                latch.countDown();
            }
        }).start();
        {
            latch.await();
            System.out.println("主线程"+Thread.currentThread().getName());
            System.out.println(local.get());
        }
    }
}

结果:

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页