详解ThreadLocal

详解 ThreadLocal

ThreadLocal引用一个对象,使得各个线程都拥有各自的这个对象,使其成为线程私有的,从而各个线程就不会争用这个对象,也就不会发生各种并发问题了。

ThreadLocal引用对象就相当于拿空间换时间,每个线程一份,不需要做并发访问控制,节省了时间开销,提升了效率。

Thread类中

ThreadLocal类有成员变量 threadLocals,因此每次实例化一个线程对象的时候,该线程对象就拥有了一个 threadLocals 成员

threadLocals 成员的类型是 ThreadLocal.ThreadLocalMap 类型的;

 

ThreadLocalMap 是 ThreadLocal类中的一个静态内部类

可以看到,在ThreadLocalMap中有一个成员变量 private Entry[] table,正式这个Entry数组用来存储本线程的所有ThreadLocal变量;

其中Entry类如下图所示:

Entry是一个弱引用的扩展类,引用就是当前的 threadLocal 对象,被引用的就是我们要给让每个线程各自有一份的那个对象;

调用 ThreadLocal 对象的 get()、set()方法,可以获取、设置这个 threadLocal 对象到底是引用的哪个对象

用一个简单的Demo来演示 ThreadLocal 的具体情况

import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {

    private static ThreadLocal<Node> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Node node1 = new Node(3, 4);
        Node node2 = new Node(5, 9);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
                threadLocal.set(node1);
                System.out.println(threadLocal.get());
                System.out.println("threadLocal = " + threadLocal);
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
                threadLocal.set(node2);
                System.out.println(threadLocal.get());
                System.out.println("threadLocal = " + threadLocal);
            }
        });

        thread1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

class Node {
    int x;
    int y;
    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "{" + x + ", " + y + "}";
    }
}

运行结果

null
{3, 4}
threadLocal = java.lang.ThreadLocal@36477dc9
null
{5, 9}
threadLocal = java.lang.ThreadLocal@36477dc9

设置一个全局静态变量的 ThreadLocal 变量 threadLocal,线程 thread1先从threadLocal获取值为null,再设置为对象node1,再获取;

thread1运行完后等待3秒,thread2 执行,先获取threadLocal的值,可以看到为 null,而thread1在前面已经设置了引用 node1,由此可见,thread1和thread2有各自独立的threadLocal,存放着各自不同的值,彼此互不干扰;

那为什么threadLocal是同一个对象,存放的值却可以不同呢?JDK是怎么实现的呢?从源码分析

 

ThreadLocal的 set()方法

先得到当前线程,再得到当前线程的 threadLocals 成员变量,前面已经分析过了,threadLocals变量是一个ThreadLocalMap类型的变量;

ThreadLocalMap利用一个 Entry[] 数组存储  (ThreadLocal<?>, Object) 键值对;

从源码可以看到,以当前threadLocal对象作为键,要存放的对象作为值;因此尽管 thread1 和 thread2 的 threadLocals是同一个对象,但是它们分别存在了各自的 threadLocals 这个map中,只不过键相同,但是值却不相同;因此两个线程存储的互不干扰。

 

ThreadLocal的 get() 方法

跟 set 方法类似,先得到当前线程的 threadLocals 成员变量,再以当前的ThreadLocal 对象 threadLocal 作为键查找其对应的引用的对象。

 

通过上面对ThreadLocal源码的分析,可以明白ThreadLocal是如何做到每个线程各自拥有,互不干扰的。

在使用ThreadLocal时还应当注意每次使用完调用 remove() 函数,以免再次调用时前面存储的值会产生影响,并且能够有效避免内存泄漏

Entry数组是一个WeakReference类的弱引用,就是为了有效防止内存泄漏的发生,下一篇讲一讲使用ThreadLocal的常见场景以及使用ThreadLocal常出现的内存泄漏现象,这也是面试官非常喜欢考察的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值