首页 > 技术文章 > ThreadLocal使用全解

WangXianSCU 2021-01-30 23:13 原文

一、何为ThreadLocal

1、ThreadLocal的含义

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

2、ThreadLocal的实用意义

每个线程拥有自己的局部变量毫无疑问比使用全局变量好,因为局部的变量只有县城自己能看见,而不会影响到其他线程。ThreadLocal本身能被多个线程共享使用,并且又能达到线程安全的目的。常用的就是get、set、initialValue等方法。

3、使用ThreadLocal前你必须知道的

JDK建议将ThreadLocal定义为private static类型。
ThreadLocal最常用的地方就是为每个线程绑定一个数据连接、HTTP请求、用户身份信息等,这样一个线程的所有调用到的方法都可以非常方便地访问这些资源。

二、ThreadLocal的使用

1、创建和初始化ThreadLocal

我们只需直接用new的方式创建ThreadLocal变量,并使用一个泛型参数指定ThreadLocal的类型。如果ThreadLocal的初始值不被设定,它的默认初始值将被设定为null,如果要更改初始值,我们通常有两种方法:

  • 使用实现了initialValue的匿名内部类
  • 使用ThreadLocal的类方法withInitial(),参数使用lambda表达式
    这两种方法都是在new一个ThreadLocal对象时使用。
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

 //如果要更改初始值
//方法一 需要一个继承了ThreadLocal父类,重写了initialValue方法的类,用它来创建ThreadLocal类对象
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(){      //这里使用匿名内部类
    @Override
    protected Integer initialValue() {
        return 200; //初始化的值由null变成200
    }
};

//方法二 使用withIntial方法
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->200);

2、获取和设置ThreadLocal的值

使用get和set方法即可

private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->200);
//获取值
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());//get获取值
//设置值
threadLocal.set(99);//set设置值

三、ThreadLocal的一个简单示例

public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
    public static void main(String[] args) {
        for(int i=0;i<5;i++){
            new Thread(new MyRun()).start();
        }
    }

    //内部类
    public static class MyRun implements Runnable{
        @Override
        public void run() {
            Integer left = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"得到了-->"+left);
            threadLocal.set(left-1);
            System.out.println(Thread.currentThread().getName()+"还剩下-->"+threadLocal.get());
        }
    }
}

四、ThreadLocal的上下文环境分析

注意,在ThreadLocal的使用中,get到的值的来源有以下规律

  • 在构造器中试图get:哪个线程调用了构造器就get到哪个线程的ThreadLocal的值
  • 在run方法中或其调用的方法中试图get:get该线程自己的ThreadLocal的值
    以下是一个示例
public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);

    public static void main(String[] args) {
        new Thread(new MyRun()).start();    //如果构造器中有读写ThreadLocal,这里设置的是main线程
    }

    //内部类
    public static class MyRun implements Runnable{
        public MyRun() {
            //构造器是哪里调用就属于哪里。这里是main调用的,所以设置和打印的都是main的ThreadLocal的值
            threadLocal.set(100);
            System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
            //new Thread(new MyRun()).start();    //如果在这里又new一个MyRun对象,就要调用构造器,其中set和get的,就是这里这个线程的ThreadLocal的值
        }
    }
}

五、ThreadLocal和Synchronized的对比

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

推荐阅读