ThreadLocal概念解释

前言

在我们学Java的时候,知道变量通常有成员变量和局部变量。局部变量在一个方法内部使用,当方法结束后,局部变量也就被清除;成员变量属于一个对象,只要我们有一个对象的访问权限,我们就可以访问这个对象的成员变量。

ThreadLocal又叫本地线程变量,是相对于以上两种变量的另一种模式。ThreadLocal和一个线程的生命周期绑定,一个线程从一个顶级方法入口进入,一层层的调用又一层一层的退出,最终从顶级方法入口结束。局部变量的作用域是线程域的某个方法内部;成员变量的作用域虽然很宽广,但是你必须拥有这个对象访问权限;本地线程的变量的作用域,属于整个线程范围,一个线程可以跨越多个方法使用本地线程变量,这就是它特殊的地方。

概念

如何要定义一个横跨多个方法,线程全局使用的对象。前面说了局部变量和成员变量都不行。因此我们要把本地线程变量放在一个公共区域,也就是说我们线程无论运行到哪个方法中,都可以访问这个共享线程变量。

public class ThreadLocalContext {
    public static final ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();
}

我们定义了一个类,它有一个static final方法,是一个整数类型的本地线程变量。所以无论你的线程走到哪里,都可以通过ThreadLocalContext.threadLocalVariable来进行访问这个整数。你可能会有疑惑,既然如此那我干嘛不用下面这种形式?

public class ThreadLocalContext {
    public static final ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();
    
    public static final int a = 0;
}

我们定义一个static final int a变量,它也是整数,我也可以在任何一个方法中访问和修改这个a,也实现了多个方法中共享使用。这两种模式虽然都可以在多个方法中共享,但是后者并不能做到线程的隔离。但是我线程1把a设置成了0,拟后面整个生命周期中都采用int a = 0的形式,但是这样存在线程安全问题。我希望的是这个a只是被某一个线程在多个方法中共享,而不是希望这个a在多个线程中共享。

ThreadLocal<Integer>可以看做是一种特殊的Integer,它比int a多了一种特殊的功能。线程1拿到这个ThreadLocal<Integer>整数对象,给她set了一个值100;其他线程拿到ThreadLocal<Integer>也可以设置另一个值90,它们并不会覆盖。线程1在后续任何方法中拿到这个ThreadLocal<Integer>对象get出来都是100,而线程get出来依然是90。

也就是说ThreadLocal<Integer>它从语义上讲和int a没有任何区别,使用上是等价的,但是它多了一个特殊功能就是线程隔离。不同线程访问它,给他赋值可以做到相互不干扰。

实现

每一个Thread线程对象的内部,都维护了一个ThreadLocalMap

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null;
    
}

set

当我们要threadLocalVariable.set(100)设置一个值的时候

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. 获取当前线程,拿到Thread
  2. 拿到当前线程的ThreadLocalMap
  3. map.set(this, value)设置值

在第三步设置值中出现了一个this,这个this表示ThreadLocal对象本身它作为key存在,而value是我们set进来的value保存在ThreadLocalMap的value上。

get

如果调用一个ThreadLocal对象的get方法,只需要把this传进去作为key,搜索ThreadLocalMap上的value。每一个操作都是先获取当先线程Thread对象,然后根据ThreadLocal对象作为key,去Thread对象内部的Map寻找这个value。这就是线程隔离的关键。

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

无论你多少个线程,即便同时去操作ThreadLocal对象的get或set方法,他们根本不会调用ThreadLocal对象的任何方法,只会把ThreadLocal对象当做一个key罢了。

为什么要用弱引用作为key

我们往ThreadLocal对象上set了一个值,本质上是往ThreadLocalMap中添加了一个entry,如果map的key是一个强引用,即便你在程序的任何地方都拿不到这个key的引用了,但是ThreadLocalMap中key指向了这个ThreadLocal对象,那么永远都不会被垃圾回收。因为有一个强引用的key指向了它。

// 弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    } 
}

// 强引用
static class Entry {
    ThreadLocal<?> key;
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        key = k;
        value = v;
    }
}

如果程序中一旦再也不存在一个强引用指向ThreadLocal对象,说明程序中已经不可访问这个ThreadLocal了。那么这个key所指向的堆上的ThreadLocal对象就被垃圾回收了,key就指向了一个null,如果还不清楚,就看下面这个例子:

public class Test {
    
    private static Map<Object, Object> map = new HashMap<>();
    
    public static void put() {
        map.put(new Object(), new Object());     
    }
    
}

map是一个全局存在,通过put设置了一个Node,如果不通过迭代器我们怎么也访问不到他们,因为key在我们调用put后就已经拿不到。所以ThreadLocalMap中使用弱引用作为key,是为了当程序中一旦不存在一个强引用指向ThreadLocal对象,说明程序中已经不能访问这个ThreadLocal了,那么这个key所指向的堆上的ThreadLocal对象就被垃圾回收了,key就指向了一个null。那么对上叙代码进行改造的话:

public class Test {
    
    private static Map<Object, Object> map = new HashMap<>();
    
    public static void put() {
        map.put(new WeakReference<>(new Object()), new Object());     
    }
    
}

一旦key的这个Object没有强引用指向了,那么map中的弱引用key就会指向一个null。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值