ThreadLocal解析

ThreadLocal是Java中用于线程局部变量的类,确保每个线程都有自己的变量副本,避免并发问题。它通过ThreadLocalMap存储变量,不支持继承,但InheritableThreadLocal允许子线程继承父线程的值。理解ThreadLocal的实现原理和使用场景对于多线程编程至关重要。
摘要由CSDN通过智能技术生成

基本概念

在多线程编程中,线程间数据共享是非常常见的问题,但是容易出现并发问题,特别是多个线程对同一共享变量进行写入时。为了保证线程安全,一般使用者在访问成员变量时需要进行适当的同步。

我们通常会使用synchronized或者Lock加锁方式进行线程同步,但是这些方式在性能方面是有一定的损耗的。那么有没有一种方式可以在保证线程安全的前提下,又不会带来太大的性能问题呢?ThreadLocal就是这样一种机制。

在这里插入图片描述

ThreadLocal是一种线程封闭的机制,它可以将数据隔离在每个线程中每个线程都拥有一份独立的数据副本, 实际操作的是自己本地内存的变量。这样一来,不同的线程之间就不会出现数据共享的问题了。

在这里插入图片描述

使用示例

下面是一个简单的使用ThreadLocal的示例代码:

public class ThreadLocalDemo {
   	//创建ThreadLocal变量
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //创建线程
        new Thread(() -> {
            //设置threadLocal本地变量的值
            threadLocal.set("hello world");
            //使用get方法获取该值
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
        }, "thread1").start();
		
        //创建线程
        new Thread(() -> {
            threadLocal.set("你好 世界");
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
        }, "thread2").start();
    }
}

在这个示例代码中,我们创建了两个线程,分别向ThreadLocal中设置了不同的值。由于ThreadLocal是线程封闭的机制,因此每个线程都拥有自己的独立的数据副本,两个线程之间不会互相影响。输出结果如下:

thread1 hello world
thread2 你好 世界

实现原理

ThreadLocal的实现原理其实比较简单,它主要依赖于ThreadLocalMap字段实现,其类图结构如下:

在这里插入图片描述

ThreadLocal的类结构比较简单,主要包含以下几个方法:

  • set:向当前线程的ThreadLocalMap中存储值
  • get:获取当前线程的ThreadLocalMap中的值
  • remove:清除当前线程的ThreadLocalMap中的值
  • initialValue:初始化值,可在子类中覆盖此方法以提供默认值

ThreadLocal源码:

public class ThreadLocal<T> {
    /**
     * 返回当前线程对应的ThreadLocalMap。
     * 这里的threadLocals是ThreadLocalMap类型的变量,作为存储多个ThreadLocal变量的容器,后面再具体解析
     */
    private ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * 在当前线程的ThreadLocalMap中设置值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //更新当前ThreadLocal变量的值,注意,这里this值ThreadLocal类的当前实例对象
            map.set(this, value);
        else
            //第一次调用就创建当前线程对应的HashMap
            createMap(t, value);
    }

    /**
     * 从当前线程的ThreadLocalMap中获取值。
     */
    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //将当前线程作为key,查找对应的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //通过this:当前实例对象获取ThreadLocal
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T) e.value;
        }
        //ThreadLocalMap为空则初始化当前线程的ThreadLocal变量值
        return setInitialValue();
    }

    /**
     * 从当前线程的ThreadLocalMap中清除值。
     */
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

    /**
     * 初始化值。
     */
    protected T initialValue() {
        return null;
    }

    /**
     * 创建一个新的ThreadLocalMap并初始化值。
     */
    void createMap(Thread t, T firstValue) {
        //传入的key值是当前ThreadLocal的this引用
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * 获取初始值,如果子类未提供初始值,则返回null。
     */
    T setInitialValue() {
        //初始化为空
        T value = initialValue();
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //第一次调用就创建当前线程对应的HashMap
            createMap(t, value);
        return value;
    }
}

注:一开始被ThreadLocalthreadLocalsThreadLocalMap搞晕了,后面发现其实很简单,直接把threadLocals给踢了,只剩下ThreadLocalThreadLocalMap,这样就好理解多了。threadLocals这玩意只是个变量名而已,不用关心。

记住ThreadLocal类对应一个变量,里面有一些对应的方法,而ThreadLocalMap是一个Map,里面有很多ThreadLocal, 取的时候通过当前线程和ThreadLocal对象的哈希码,找到对应的Entry对象,然后返回它的value字段。

ThreadLocal类中,主要是通过对当前线程的ThreadLocalMap的操作来实现线程本地变量的存储和获取,具体实现细节可以通过源码来了解。

ThreadLocalMap

Thread类中有一个ThreadLocalMap类型的字段threadLocals,它是线程本地变量的存储容器。每个ThreadLocal对象都有一个对应的entrythreadLocals中,用来存储线程对应的值。

ThreadLocalMap是一个自定义的HashMap,它的键为ThreadLocal对象,值为线程保存的数据对象。在每个线程中, ThreadLocalMap都是独立的,因此不同的线程之间不会出现数据共享的问题。由于ThreadLocalMap是线程独立的,因此在多线程环境下不会出现竞争的问题,从而保证了线程安全性。

ThreadLocalMap中的键值对的生命周期是跟随线程的,当线程结束后,ThreadLocalMap中的键值对也会被回收。如何线程一直不消亡,会导致这些本地变量一直存在,所以可能造成内存溢出,因此,使用结束后记得要调用ThreadLocalremove方法删除对应线程ThreadLocalMap中存储的本地变量。

ThreadLocal对象通过调用ThreadLocalset方法将值存储到当前线程的ThreadLocalMap中,通过调用ThreadLocalget方法可以获取当前线程的值。ThreadLocalremove方法可以清除当前线程中的ThreadLocalMap中的键值对,从而释放资源。

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

/*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
*/
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

在这里插入图片描述

Thread类中使用到两个变量:threadLocals inheritableThreadLocals ,都是 ThreadLocalMap类型的变量。

ThreadLocals线程本地变量的存储容器。每个ThreadLocal对象都有一个对应的entryThreadLocals中,用来存储线程对应的值。这里也就刚好引出另一个问题了:为什么threadLocals被设计成map结构?

很明显,ThreadLocals要作为容器存储多个ThreadLocal本地变量,并通过每个线程进行关联,自然我们就想到了map了。

ThreadLocal不支持继承性

ThreadLocal不支持继承性,也就是说,子线程无法访问父线程中的ThreadLocal变量,因为子线程中的ThreadLocal变量是子线程自己的,而不是从父线程中继承的。

举个例子,假设有一个父线程和一个子线程,父线程中有一个ThreadLocal变量,当父线程启动子线程时,子线程无法访问父线程中的ThreadLocal变量,因为子线程中的ThreadLocal变量是子线程自己的,而不是从父线程中继承的。

public class ThreadLocalExample {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("Hello, World!");

        Thread thread = new Thread(() -> {
            System.out.println("Child thread value: " + threadLocal.get());
        });
        thread.start();

        System.out.println("Main thread value: " + threadLocal.get());
    }
}

运行结果如下:

Main thread value: Hello, World!
Child thread value: null

可以看到,子线程中的ThreadLocal变量值为null,而不是从父线程中继承的值。

那有没有办法可以让子线程能够访问到父线程中的值呢?当然!

InheritableThreadLocal类

InheritableThreadLocal类是Java中的一个线程本地变量类,它继承了ThreadLocal类,扩展了ThreadLocal以提供从父线程到子线程的值继承

下面是一个使用InheritableThreadLocal类的例子:

public class InheritableThreadLocalExample {
    public static void main(String[] args) {
        InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
        inheritableThreadLocal.set("Hello World");

        Thread thread = new Thread(() -> {
            System.out.println(inheritableThreadLocal.get());
        });

        thread.start();
    }
}

在这个例子中,我们创建了一个InheritableThreadLocal对象,然后在主线程中设置了一个值。接着,我们创建了一个子线程,并在子线程中获取了这个值。由于InheritableThreadLocal类提供了从父线程到子线程的值继承,所以子线程中可以获取到这个值,输出结果为"Hello World"

使用场景

InheritableThreadLocal类的应用场景是当线程需要从父线程继承某些值时,可以使用InheritableThreadLocal类。

例如,当需要在多个线程之间共享用户ID或事务ID等线程本地变量时,可以使用InheritableThreadLocal类。InheritableThreadLocal提供了从父线程到子线程的值继承,因此子线程可以获取父线程中的值。

源码分析

Thread类中定义了叫inheritableThreadLocals的变量,类型为ThreadLocalMap

 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

InheritableThreadLocal类中继承了ThreadLocal,并重写了三个方法,替换了原有的threadLocals

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * 用于在子线程中创建一个新的InheritableThreadLocal对象时,为该对象提供一个初始值。
     * 在默认情况下,该方法返回父线程中的值,因此子线程中的InheritableThreadLocal对象的初始值与父线程中的值相同。
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * 获取的是inheritableThreadLocals变量
     * 而不再是ThreadLocals变量
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 创建当前线程的inheritableThreadLocals变量实例
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

再具体看一下childValue方法的执行流程,以及如何访问父线程的本地变量

当我们在主线程中start一个子线程时,会new 一个Thread。创建线程时发生了什么才让父子线程的InheritableThreadLocal可以传递

Thread类中,有多个默认构造方法, 经过重载,都最终调用了init方法

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
}


//最终调用
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {


    	//获取当前线程(父线程)
        Thread parent = currentThread();
       	……
    	/* 如果inheritThreadLocals为true并且父线程中存在InheritableThreadLocal对象,
    	* 则使用ThreadLocal.createInheritedMap方法创建一个新的Map对象,
    	* 该对象包含父线程中所有InheritableThreadLocal对象的值。这个新的Map对象将作为
    	* 子线程的InheritableThreadLocalMap对象的初始值。
    	*/
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    }
inheritThreadLocals变量的值复制一份到新的//创建一个新的Map对象
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
}


private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

    		//将父线程的inheritThreadLocals变量的值复制一份到新的对象中
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        //这里调用的是InheritableThreadLocal重写后的childValue方法
                        Object value = key.childValue(e.value);//返回e.value
                        Entry c = new Entry(key, value);
                        //计算hash值
                        int h = key.threadLocalHashCode & (len - 1);
                        //找到一个空的位置添加Entry实例,就是hashmap添加元素的过程
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
}

总结

ThreadLocal是一种线程封闭的机制,它可以将数据隔离在每个线程中每个线程都拥有一份独立的数据副本

ThreadLocal主要是通过ThreadLocalMapThread类中的ThreadLocalMap字段实现,ThreadLocalMap是一个自定义的HashMap,用来存储线程本地变量的键值对,而Thread类中的ThreadLocalMap字段threadLocals则是用来存储每个线程的ThreadLocalMap

通过使用ThreadLocal,我们可以在保证线程安全的前提下,又不会带来太大的性能问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值