likes
comments
collection
share

Java并发编程之ThreadLocal(一)

作者站长头像
站长
· 阅读数 10

简介

当多线程访问共享变量时,往往会发生并发问题,导致获取的数据不准确、不一致,解决这个问题的一种方法就是进行加锁,同时只让一个线程访问共享变量,但是加锁就意味着有额外的资源消耗。而ThreadLocal是另一种可以解决此问题的方案,其基本原理是每个线程创建的变量都保存在线程本地,多线程之间互不影响,这样就能避免访问共享变量出现的并发问题了。除此之外,ThreadLocal也解决了一个线程在多个方法中获取同一变量的问题。

基本使用

@Slf4j
public class Test6 {
    // 创建ThreadLocal对象
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            threadLocal.set("t1存的数据");  // 存储数据
            log.info(threadLocal.get());   // 获取数据
        }, "t1").start();

        new Thread(() -> {
            threadLocal.set("t2存的数据");  // 存储数据
            log.info(threadLocal.get());   // 获取数据
        }, "t2").start();
    }
}

Java并发编程之ThreadLocal(一)

首先创建了一个ThreadLocal对象并指定存储的数据类型为String,然后创建了两个线程t1t2,分别存储数据到threadLocal中,然后再从threadLocal中取出数据并打印出来,根据输出结果可以看到t1线程获取到的数据是"t1存的数据",t2线程获取到的是"t2存的线程",实现了线程间的数据隔离。

类结构

Java并发编程之ThreadLocal(一)

Entry

上图是通过ThreadLocal存储数据的类结构图,最内部是通过Entry对象以K-V形式来存储数据,其中KeyThreadLocal对象,value就是要存储的数据。Entry类源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
// Entry的父类WeakReference
public WeakReference(T referent) {
    super(referent);
}
// WeakReference的父类Reference
Reference(T referent) {
    this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;  // ThreadLocal存储到了referent属性
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

可以看到Entry的属性value就是我们要存的数据,而ThreadLocal类型的key最终存到了祖父类Reference的属性referent中。

ThreadLocalMap

// 存储数据的数组
private Entry[] table; 

// 构造方法
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);
}

上面是ThreadLocalMap源码中的一部分,其中table是一个Entry数组,可以用来存储多组数据。构造方法中会对table进行初始化并执行存储数据操作,后面会介绍操作流程,这里只要了解其结构即可。

Thread

ThreadLocal.ThreadLocalMap threadLocals = null;

Thread类中有一个ThreadLocalMap类型的属性threadLocals,所以通过ThreadLocal存储的数据都是存在每个线程内部,所以多线程之间做到了数据隔离。

综上,Thread类维护了一个ThreadLocalMap,而ThreadLocalMap中维护了一个Entry数组用来存储多个ThreadLocal的数据,Entry中存储的是一个ThreadLocal的数据。

转载自:https://juejin.cn/post/7231932399100706872
评论
请登录