ThreadLocal

ThreadLocal

用户线程局部变量,在多线程环境下可以保证线程里面的变量独立于其他线程里面的变量,也就是说,每个线程中设置的变量在其他线程里面不可以访问,类似于线程的 private static
内部结构图:
在这里插入图片描述

从图中就可以观察出 ThreadLocal 的核心机制:

(1)每个线程内有一个本地私有的 ThreadLocals 变量,其是一个 ThreadLocalMap 类似(类似于 HashMap)
(2)通过 ThreadLocal 来维护每个线程中设置的局部变量,设置的变量值以键值对形式储存于每个线程的 ThreadLocals ,并不是储存于 ThreadLocal 中,其中 key 为当前 ThreadLocal 弱引用(它继承了 weakReference),value 为设置的局部变量值
(3)ThreadLocalMap 中一个键值对的 key 对应一个 ThreadLocal ,一个 ThreadLocal 只能维护该线程副本中的一个键值对中的 value 值,不同的 ThreadLocal 在同一个线程总设置局部变量,分别以不同的键值对储存在 ThreadLocalMap 中

使用原理:

Thread 类中有两个变量,ThreadLocals 和 InheritebleThreadLocals ,这两个变量都是 ThreadLocal 中的 ThreadLocalMap 类型的,ThreadLocalMap 就类似于 HashMap 的储存结构,以键值对的形式储存数据,ThreadLocal 类似于一个工具类,它是一个空壳的存在,也就是说当要存储某个线程私有的变量时,这个私有设置的变量不会储存到 ThreadLocal 实例中,都是储存在线程本地的变量 ThreadLocals 中,而 ThreadLocals 是每个线程独有的,调用 ThreadLocal 的 set() 方法来向 ThreadLocalMap 中添加键值对,键值对的 key 是当前的 ThreadLocal 的弱引用,value 是set() 方法设置的变量,通过调用 ThreadLocal 的 get() 方法获取到该线程的 ThreadLocalMap 然后获取到对应线程设置的变量

所以说具体给每个线程设置的独有变量都是存放在该线程的 ThreadLocals 变量中,ThreadLocals 变量又是 ThreadLocalMap 类型,以键值对的形式储存数据,所以因此达到了每个线程内设置的变量独立于其他线程,只有该线程可以访问

示例:
package lesson1;

public class ThreadLocalTest {
    // 提供一个线程的局部变量,每个线程中独享,不能和其他线程共享
    // 在一个线程中设置一个值,不影响其在其他线程中设置的值
    private static ThreadLocal<String> local = new ThreadLocal<String>();
    public static void main(String[] args) {
        local.set("Main");
        new Thread(new Runnable() {
            public void run() {
                local.set("Thread");
                System.out.println(local.get() + "1");
                local.remove();
            }
        }).start();
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(local.get() + "2");
        local.remove();
    }
}

输出结果为:
Thread1
Main2

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);
}
步骤:

1.获取当前线程的成员变量map
2.map非空,则重新将ThreadLocal和新的value副本放入到map中
3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中

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();
}
步骤:

1.获取当前线程的ThreadLocalMap对象threadLocals
2.从map中获取线程存储的K-V Entry节点
3.从Entry节点获取存储的Value副本值返回
4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException

remove

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

ThreadLocal 支持继承性吗,也就是子类继承父类,那么子类能访问到父类中 ThreadLocal 设置的变量吗

答案是:不能,原因就和 ThreadLocal 原理中解释的一样,ThreadLocal 调用 set() 方法设置的线程私有变量是储存在 当前线程的 ThreadLocals 中的 ThreadLocalMap 类型的结构中,所以其他线程就无法访问

解决:如何子类能访问到父类线程设置的变量,通过 InheritableThreadLocal 类

InheritableThreadLocal 能够支持子线程访问父线程的本地变量,具体原因如下:

首先:InheritableThreadLocal 继承了 ThreadLocal 类,重写了 childValue 、getMap 、createMap 三个方法,通过重写 createMap 方法,创建的就不是线程中的 ThreadLocals 变量,而是创建 InheritableThreadLocals 变量,它也是 ThreadLocalMap 类型的变量,也是同样通过类似于 HashMap 以键值对的形式储存数据,同样调用 get() 方法返回的也不是 ThreadLocals 变量了 ,而是 InheritableThreadLocals 变量
之所以InheritableThreadLocal 能支持子类线程访问父线程的本地变量,是因为,在构造函数中会将InheritableThreadLocals 变量里的内容复制一份到新的 ThreadLocalMap 中,然后赋值给子线程的 InheritableThreadLocals 变量中,这样子线程就可以访问父线程的本地变量了

内存泄漏问题

(1)内存泄漏情况一:ThreadLocalMap 中键值对的 key 是当前 ThreadLocal 的弱引用,value 为 set() 方法设置的本地变量,之所以要设置 Key 为 ThreadLocal 的弱引用是因为,假设现在设置的是 强引用,而 ThreadLocalMap 和当前线程的生命周期相同,当引用 ThreadLocal 的对象被回收之后,ThreadLocalMap 中的 ThreadLocal 和 value 还是强引用,无法被回收,那么 GC 无法回收而且引用 ThreadLocal 的变量也被回收了,也就无法调用 ThreadLocal 的 remove() 方法对其清除,那么此时就会造成内存泄漏
(2)内存泄漏情况二:即使 ThreadLocal 是弱引用,ThreadLocal 被 gc 回收后也会存在 key 为 null ,而 value 还是存在无法回收的情况
解决方法:每次在使用完一个线程的 ThreadLocal 操作,对该线程的本地变量使用完之后要及时清除,及时调用 remove 操作,在 get()、set()、resize()(重新调整数组大小)时,都会清除 ThreadLocalMap 中 key 为 null 的 value,这样就能降低内存泄漏的发生概率

ThreadLocalMap 发生 Hash 冲突

在 HashMap 中通过开散列的方式来解决 Hash 冲突问题,每个 bucket 数组的元素又是一个链表或者红黑树,发生 hash 冲突的元素储存在同一个链表或者红黑树中,而 ThreadLocalMap 中处理 hash 冲突的方法采用闭散列的方式,如果通过 key 的 hashCode 值定位数组中的位置时,发现位置上已经存储了其他节点,那么通过线性探测的方式找到下一个空节点位置储存
缺点:这种方法处理 hash 冲突的效率十分低下,如果存在大量不同的 ThreadLocal 向同一个线程中设置局部变量时,那么容易产生 hash 冲突,就会造成不断的去寻找下一个空节点位置储存,效率低下
解决办法:所以尽量只通过一个 ThreadLocal 来为线程设置一个局部变量,那么每次 ThreadLocal 维护变量时,都是操作的同一个节点位置,自然不会尝试 hash 冲突

ThreadLocal 线程安全问题:

如果使用 ThreadLocal 为每个线程创建一个独立私有的变量,那么这个变量会以键值对形式储存在该线程本地的 ThreadLocals 当中,ThreadLocals 是 HashMap 类型,其中储存的 key 为当前 ThreadLocal 的弱引用,value 为设置的变量,那在这种为每个线程设置私有变量的情况下是线程安全的,因为都是设置线程本地的 ThreadLocals 当中,那么自然就不涉及多线程同时访问一个共享变量的安全问题

而如果我们将线程中的ThreadLocal.get() 副本取出,要是只在当前线程中使用,那么还是线程安全的,如果提供给其他的多线程同时访问,那么就是不安全的
例如:

public class Test5 {
    static class Person {
        int num;
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<Person> threadLocal = new ThreadLocal<>();
        threadLocal.set(new Person());
        System.out.println(threadLocal.get().num);
        Person person = threadLocal.get();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    person.num++;
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    person.num++;
                }
            }
        });
        thread1.start();
        thread2.start();
        thread2.join();
        thread1.join();
        System.out.println(threadLocal.get().num);
    }
}
// 结果不唯一,有时候为18699
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值