【Java】万万没想到,又被问ThreadLocal了

我待ThreadLocal如初恋,ThreadLocal虐我千百遍。但这一次,要彻底搞懂ThreadLocal

回顾一下面试名场面:

面试官:Handler如何做到与线程绑定的?

我:每个Handler只有一个相关联的Looper,线程绑定关键点正是Looper中和其内部的ThreadLocal类型的变量sThreadLocal。通过ThreadLocal完成了Looper和线程的绑定。

问:ThreadLocal原理是什么?

我内心:好像有个map,有个泛型,死活想不起啊。。。

10S后

我内心:我嘞个擦,ThreadLocal到底是什么鬼啊,为什么会有这么奇!怪!的!类!啊

【Java】万万没想到,又被问ThreadLocal了_第1张图片

打开源码开干吧

目录

一.ThreadLocal源码注释及使用方法

二.关键方法get和set

1.set

2.get

三.看看Looper怎么做的

三.总结


一.ThreadLocal源码注释及使用方法

看看第一段注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

此类提供线程局部变量。这些变量与正常变量的不同之处在于,访问一个变量的每个线程(通过其 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,并将其状态与线程相关联(例如,用户 ID 或事务 ID)。

大概意思就是:ThreadLocal这个变量,顾名思义,就是用来管理(设置和获取)线程的局部变量的一个工具类。你要用这个ThreadLocal的时候,最好就是把这个定义成一个私有的静态变量。在不同的线程中,调用这个静态变量的set和get方法,都能设置或者获取属于自己的线程局部变量。当然这个变量的类型也是有限制的,就是ThreadLocal的泛型类型。

这里有个关键点:ThreadLocal通常定义成静态变量。

官方怕你不会用,注释第二段就给了一个例子

  import java.util.concurrent.atomic.AtomicInteger;
 
  public class ThreadId {
      // 原子整型,表示下一个要分配给下一个线程的id(这个线程ID是我们自己定义的,跟线程自身id不同)
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);

      // 重点:ThreadLocal变量,包含我们给每个线程定义的id(这里的ThreadLocal对象是个私有静态变量)
      // Thread local variable containing each thread's ID
      private static final ThreadLocal threadId =
          new ThreadLocal() {
              @Override protected Integer initialValue() {
                  // 初始化方法,当调用get方法第一次获取线程id的时候会初始化。
                  return nextId.getAndIncrement();
          }
      };

      // 返回当前线程的唯一id。如果没有的话就给他分配一个。
      // Returns the current thread's unique ID, assigning it if necessary
      public static int get() {
          return threadId.get();
      }
  }

 

上面例子里的ThreadId用起来是啥效果呢?我写个demo跑一下


 

val thread1 = Thread {
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
}
val thread2 = Thread {
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
}

thread1.start()
thread2.start()

//  打印结果如下
//  thread1 :0
//  thread2 :1
//  thread2 :1
//  thread1 :0

通过ThreadId这个工具类,可以很方便的去获取到自定义的线程id。

代码注释中会高频地出现一下两个名词:

ThreadLocal实例(ThreadLocal instances)

线程局部变量副本(copy of a thread-local variable)

这个局部变量副本很容易让人迷惑和误解。为了方便理解,我把ThreadLocal实例(也就是定义的那个静态变量)理解为 “ThreadLocal工具对象” ,把线程局部变量副本就叫做 “线程局部变量” 。线程中调用ThreadLocal工具对象可以方便地存取指定类型的变量。

第三段注释:

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都对他的线程局部变量副本持有隐式引用。当线程销毁后,线程局部变量实例的所有副本都将可能被垃圾回收掉(除非存在对这些副本的其他引用)。

大概意思就是:

只要线程还活着,ThreadLocal对象还能访问,每个线程对他的线程局部变量就有引用。线程销毁之后,线程局部变量就会成为垃圾回收的对象。

二.关键方法get和set

ThreadLocal中有get和set方法,分别用来获取和设置当前线程的“线程局部变量”。

1.set

先看看set方法和其相关的方法,这三个方法都是定义在ThreadLocal中:


 

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 如果map不为null,添加键值对 “ThreadLocal工具对象:value”
        map.set(this, value);
    } else {
        // 如果为null,给map初始化并添加键值对
        createMap(t, value);
    }
}

/**
* 获取线程的threadLocals成员变量,类型为ThreadLocal.ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
* 为线程t创建ThreadLocalMap对象,构造函数参数为ThreadLocal工具对象,以及初始值
*/
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

说明一下,这里出现了一个新的类ThreadLocalMap,它是定义在ThreadLocal中的静态内部类,内部是一个哈希表,保存键值对,只不过保存键值对的key必须是ThreadLocal类型的,值就是上面说的“线程局部变量”。

ThreadLocalMap便是保存“ThreadLocal:Object”的哈希表。

【Java】万万没想到,又被问ThreadLocal了_第2张图片

那我又问了,Thread的ThreadLocalMap的成员变量是什么样的,就下面这样的,不仅有还有两个,我们用的是第一个。

public class Thread implements Runnable {

    // ... 省略

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    // ... 省略
}

 

到这里要总结一下了,为了方便理解,用非常不专业的uml图,看看Thread、ThreadLocal、ThreadLocalMap他们三个的引用关系:

【Java】万万没想到,又被问ThreadLocal了_第3张图片

ThreadLocal以自身为key,从调用线程中获取value。

关键点来了:ThreadLocal乃真谋士,以身为饵(key),引天下入局。

2.get

继续看get方法

    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 获取当前ThreadLocal工具类为key对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // map为null则返回初始值,默认初始值为null
        return setInitialValue();
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal) this);
        }
        return value;
    }

    protected T initialValue() {
        return null;
    }

 

set看懂的话,get就很容易理解,唯一要注意的是获取初始值initialValue这个方法可能在实际使用时可能被重写。

三.看看Looper怎么做的

Looper中用到ThreadLocal的地方全都在这里了:

public final class Looper {
    // 此处省略
    static final ThreadLocal sThreadLocal = new ThreadLocal();
    // 此处省略

    public static void prepare() {
        prepare(true);
    }

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

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    // 此处省略
}

 

sThreadLocal为静态变量。

prepare方法中为当前线程设置Looper。

三.总结

这个ThreadLocal写法确实剑走偏锋,常规思维容易记混乱,不过记住两个关键点就可以把整个逻辑回想起来:

1.ThreadLocal通常定义成静态变量

2.ThreadLocal以自身为key,从当前Thread的map中获取value

好了,至此分享结束。

此刻我的心情是:面试官快来问我ThreadLocal的问题吧,我已经迫不及待地想回答了。

 

你可能感兴趣的:(java,数据库,jvm)