Java 中 ThreadLocal 原理分析

前言

ThreadLocal 出现主要是将变量进行线程隔离,把变量的值跟线程绑定。

ThreadLocal 可以理解为 Map<Thread, T>,但实际 ThreadLocal 的实现并不是这样,具体的实现在文章后说明。

当两个线程同时访问一个 ThreadLocal 变量时,他们只能看到自己的值,互相之间并不影响,所以 ThreadLocal 保存的数据是线程私有的。

ThreadLocal 出现是为了解决什么问题?

由于通过 ThreadLocal 设置的变量,会变成线程私有,所以对这些变量的访问就不再需要进行同步来保证线程安全性了。

目前了解到应用场景主要有两个:

  • 解决在函数嵌套调用时,多个方法间需传递线程局部变量问题;如,线程有一个局部变量 a,要将这个变量 a 传递给多个嵌套调用的函数: funA() -> funB() … funZ(),这样在每个函数的方法签名上都需要包含变量 a,而使用 ThreadLocal 可以解决这个问题。
  • 作为某个类的全局静态变量,确保每个线程只拥有这个类的一个对象实例。如,在 Android 的 Looper 源码使用静态的 ThreadLocal 对象,来保证每个需要 Looper 的线程只有一个 Looper 对象。相关源码如下,
  1. 初始化 ThreadLocal 对象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  1. Looper.prepare() 方法,通过 sThreadLocal 判断当前线程是否已经有 Looper 对象,如果有就会抛出一个 RuntimeException,如果没有就会创建一个 Looper 对象,并设置到 sThreadLocal 中。这样就保证了每个线程最多拥有一个 Looper 对象。
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
  1. Looper.myLooper() 方法,通过 sThreadLocal 获取当前线程的 Looper 对象
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
ThreadLocal 使用用例
public class ThreadLocalExample {
    public static class MyRunnable implements Runnable {

        private ThreadLocal<Integer> threadLocal =
               new ThreadLocal<Integer>();

        @Override
        public void run() {
            threadLocal.set( (int) (Math.random() * 100D) );
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
    
            System.out.println(threadLocal.get());
        }
    }


    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}

这个用例中,给 thread1 和 thread2 线程传递了同一个 MyRunnable 对象,在 MyRunnable 对象中,有一个 ThreadLocal 对象。根据 ThreadLocal 的特性,设置的变量的值会跟线程绑定,所以 thread1 和 thread2 设置的值是分开的,互不影响,见下面的输出,
Output

61
49

ThreadLocal 原理分析

ThreadLocal 实现原理主要有三个相关类,分别是 Thread,ThreadLocal, ThreadLocalMap<ThreadLocal, T>,他们的关系如下,
在这里插入图片描述
当前线程 Thread 有一个 ThreadLocal.ThreadLocalMap 类型的域 threadLocals.

每次线程调用 ThreadLocal 的 set(), get(), remove() 方法时,都会有如下步骤,

  1. 通过 Thread.currentThread() 拿到当前线程对象 t;
  2. 获取当前线程 t 的 threadLocals(ThreadLocalMap) 域;
  3. 对 t.threadLocals 进行 set(), get(), remove() 操作

所以,实际上操作的还是当前线程的 threadLocals 域,这个域并没有在线程间共享。

以示例中的代码为例,ThreadLocal 对象只有一个,ThreadLocalMap 和 Thread 一对一,分别有两个对象。

上面的示例中,只有一个 ThreadLocal 对象,当有多个 ThreadLocal 对象时,在线程 t 上为多个 ThreadLocal 对象设置 value,这些 value 都保存在线程 t 的 threadLocals (ThreadLocalMap) 域上,以 ThreadLocal 对象为 key,设置的 value 为值。

总结

ThreadLocal 的原理,不能单独来讲,需要结合 Thread 和 ThreadLocalMap 一起分析,因为在 Java 类库的实现中是将这三个类组织起来实现的 ThreadLocal。

ThreadLocal 有一些特定的应用场景,如变量传递、限制线程拥有某个类对象的个数等。

对 ThreadLocal 的操作实际上都是对 Thread 的 threadLocals 域进行操作,这也是在 ThreadLocal 上设置变量时能够达到线程隔离的原因。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ToSimpleL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值