Java中的ThreadLocal详解

写在前面

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。本篇文章将带你详细的剖析一下ThreadLocal,以及在开发中的使用。

代码分析

首先ThreadLocal这个类是位于java.lang这个包中,具体源码可以自行查看。那么ThreadLocal到底是干嘛用的呢?其实它主要就用来在当前线程中存取变量用的,存取变量就存取变量怎么多了一句当前线程中?不要急接着往下看。说到存取变量我们平时可能用到的例如:List,Map,数组等,那么ThreadLocal又是通过什么样方式进行变量的存取呢?答案就是Map(键值对),没错就是Map,而这个Map不是其他的Map而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,在每一个线程Thread中都有一个ThreadLocalMap的成员变量,不信的话看一下Thread类的源码:

public class Thread implements Runnable {
  .......这里省略掉之前的代码

       ThreadLocal.ThreadLocalMap threadLocals = null;

 .......这里省略掉之后的代码
}

这里你会发现在Thread类中确实是有一个ThreadLocalMap的成员变量threadLocals,可以很明确的告诉你ThreadLocal就是在操作这个Thread中的threadLocals,来进行变量的存取,置于为什么ThreadLocal能操作Thread中的threadLocals,以及如何操作的后面通过源码的分析更进一步的了解。截止到目前你需要知道以下几点:

  • 1、 ThreadLocal是在用于存取变量的
  • 2、 ThreadLocal是在当前线程中存取变量的
  • 3、 ThreadLocal是采用Map,也就是键值对的形式进行变量存取的

我们先看看如何使用的,然后在分析分析源码,例如下面就是使用ThreadLocal的一个例子:

public class MyClass {

    //定义了两个ThreadLocal分别为:local1,local2
    private static ThreadLocal local1 = new ThreadLocal<>();

    private static ThreadLocal local2 = new ThreadLocal() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    public static void main(String[] args) {
        System.out.println("local1:" + local1.get());//运行结果:local1:null
        System.out.println("local2:" + local2.get());//运行结果:local2:false
        local1.set("我是local1");
        local2.set(true);
        System.out.println("local1:" + local1.get());//运行结果:local1:我是local1
        System.out.println("local2:" + local2.get());//运行结果:local2:true
    }
}

通过以上代码你会发现我在未进行任何存储操作即未调用set(T t)方法之前:local1返回的是null,local2返回的却是false;这就和local2重写了initialValue方法有关,具体请看ThreadLocal源码分析(这里剔除掉了和我们使用不相干的代码):

package java.lang;//所在包

public class ThreadLocal {
     ...  //省略掉之前的代码
 
    //这个方法看到没是protected的,也就是说希望我们重写的,默认返回null
   //初始化值,在未存储任何值的前提下,去取值返回的结果,默认返回null
    protected T initialValue() {
        return null;
    }

   //从ThreadLocalMap中获取存储的变量
    public T get() {
         //先获取当前的线程对象
        Thread t = Thread.currentThread();
        //再获取线程对象中的ThreadLocalMap成员变量:threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
         //如果不为空,则以自己注意是自己为键,来获取对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
              //如果value不为空,则直接返回
                return (T)e.value;
        }
        return setInitialValue();
    }

 //往ThreadLocalMap中加入一个变量
    public void set(T value) {
       //先获取当前的线程对象
        Thread t = Thread.currentThread();
      //再获取线程对象中的ThreadLocalMap成员变量:threadLocals
        ThreadLocalMap map = getMap(t);
      //判断返回的ThreadLocalMap是否为空
        if (map != null)
           //如果不为空,则以自己注意是自己为键,value参数为值保寸到线程的ThreadLocalMap中
            map.set(this, value);
        else
          //如果为空,则创建一个ThreadLocalMap,并以自己注意是自己为键,value参数为值,保存到该Map中同时将该Map赋值给当前线程中的ThreadLocalMap成员变量:threadLocals
            createMap(t, value);
    }


 //移除掉ThreadLocalMap存储的变量
 //该方法在jdk1.5加入
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
   
     static class ThreadLocalMap {
         //省略ThreadLocalMap的具体实现
           ......
           ......
     }
}

其实我们在使用ThreadLocal进行变量存取操作的时候用到的也就是以上的几个方法:initialValue(),get(),set(),remove()。类比Map的操作就好,set就是存,get就是取,remove就是删。

这里需要注意的是ThreadLocal进行变量存储的时候都是以自身(即this)为键来进行存储的,所以存储的是set方法而不是put或add,因为一个ThreadLocal对象就是一个键,一个键在Map中只能对应一个值;你要想往当前线程的Map中存几个不同的值,那就需要几个不同的键,即要创建几个ThreadLocal对像,当然不同线程中也可以同时使用同一个ThreadLocal作为键来存取不同的值,他们之间对值的操作互不影响。打个比方:

我用同一个身份证号(同一个ThreadLocal对象),在A商场(线程A)注册了VIP,在B商场(线程B)也注册了VIP,有一天我将A商场的VIP改变为了SVIP,那我在B商场依旧是VIP而不会因为我改变了A商场的身份会影响到我B商场。

总结

ThreadLocal其实还是蛮有用的在Android的UI线程中Handler的Looper就是通过ThreadLocal存储在当前线程中的,所以你只要在UI线程中创建的Handler用的都是之前已经在该线程存储好的Looper。一些开源库其实很多都用到了ThreadLocal,例如何洪辉的EventBus,其他的这里不做过多介绍,有兴趣的可以去看看一些好的开源库的代码,收获会很大。

你可能感兴趣的:(Java中的ThreadLocal详解)