剖析JDK源码-ThreadLocal-(11)
一、简述
即便是第一次见,可以大概的猜一下这是一个什么类;ThreadLocal直译“线程+局部的”,显然是在描述或者表示的是线程局部性的东西。那究竟是什么东东呢?
官方的中文术语是线程的局部变量。我们都知道全局变量和局部变量的区别,定义在类中方法体外的是全局的变量,该类中各个方法都能访问使用,而局部变量定义在方法中,只能在该方法内访问使用。
那为什么要把一个局部的东西封装成一个类呢?
- 为了解决多线程程序的并发问题提供了一种新的思路。
- 使用这个工具类可以很简洁地编写出优美的多线程程序。
- 为线程的安全提供一种方式。
其实就是把本该写在一个方法体内的局部变量抽离出来封装成的一个类,然后在该方法中只需要实例化这个类,就可以获得本该属于它的私有变量。这样去简化代码的方式是很常见的。
至于安全性,由于这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。
生命周期:只要线程存活并且ThreadLocal实例可以访问,每个线程都保存对其线程局部变量副本的隐式引用; 线程消失后,线程本地实例的所有副本都将被垃圾收集(除非存在对这些副本的其他引用)。
二、类的声明
多态类,在旧版本并不支持泛型的。
public class ThreadLocal<T> {
//下一个哈希码的值
private final int threadLocalHashCode = nextHashCode();
//获取下一个哈希码。自动更新从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
//两个哈希码间的差距
private static final int HASH_INCREMENT = 0x61c88647;
//计算下一个哈希码
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
- 通过以上操作生成每个线程本地变量的唯一标识符(哈希码),线程会根据这个标识符找到属于它的局部变量。这种标记在多线程下是必须存在的。
- threadLocalHashCode,首先定义一个私有属性来存储这个哈希码。
- nextHashCode ,实例化一个计算下一个哈希码的对象,初始值为0.
- 通过内部方法nextHashCode()去调用对象nextHashCode的getAndAdd()获取哈希码。
- HASH_INCREMENT,定义了两个哈希码间的间距,这里可以先看成是接口getAndAdd()需要的一个参数,然后就会返回一个新的哈希码;至于它内部的实现无需知道。
三、构造方法
public ThreadLocal() {
}
- 创建线程局部变量。没有多余的参数操作,一经实例化,上面的哈希码就会生成。
四、内部方法
1、 private static int nextHashCode();在前面说过了,就是一个生成哈希码的方法。
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
2、protected T initialValue();返回当前线程的这个线程局部变量的“初始值”。该方法将在线程第一次使用get方法访问变量时被调用,除非线程之前调用了set方法,在这种情况下,将不会为线程调用initialValue方法。通常,每个线程最多只调用一次此方法,但在后续调用remove和get的情况下,可以再次调用该方法。
这个实现默认只是返回null;如果希望线程局部变量的初始值不是null,可以进行重写这个方法。
protected T initialValue() {
return null;
}
3、public static < S > ThreadLocal< S > withInitial(Supplier<? extends S> supplier);
创建线程局部变量。变量的初始值是通过调用者的get方法来确定的。
这个方法是在1.8加入的,显然是initialValue不能满足开发的需求而增加的。它可以更高级灵活的去初始化这个局部变量。
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
4、public T get(); 返回该线程局部变量在当前线程副本中的值。如果该变量对于当前线程没有值,则首先将其初始化为调用initialValue方法返回的值。
setInitialValue() 是set()的变体,以建立initialValue。用于在用户已覆盖set()的情况下替代set()
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();
}
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);
return value;
}
5、public void set(T value);将当前线程的此线程局部变量的副本设置为指定的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
6、public void remove() ;删除此线程局部变量的当前线程的值。当这个线程局部变量随后被当前线程读取,它的值将通过调用它的initialValue()方法重新初始化,除非它的值是由当前线程在过渡期间设置的。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
五、测试
package com.albert.annotation;
public class Test{
//实例化一个线程部变量
ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
public void set() {
//将线程id寸入局部变量中
threadLocal.set(Thread.currentThread().getId());
}
public Long get() {
//获取变量
return threadLocal.get();
}
public static void main(String[] args) throws Exception{
Test test = new Test();
System.out.println("存主线程局部变量");
//存值
test.set();
//读取
System.out.println("主线程局部变量:"+test.get());
//子线程
Thread myThread = new Thread(){
@Override
public void run() {
System.out.println("存子线程局部变量");
test.set();
System.out.println("子线程局部变量:"+test.get());
}
};
//启动子线程
myThread.start();
//子线程强执行
myThread.join();
System.out.println("现在是谁的局部变量?:"+test.get());
}
}
看结果
存主线程局部变量
主线程局部变量:1
存子线程局部变量
子线程局部变量:13
现在是谁的局部变量?:1
即便国产中有子线程调用了test对象,但后面仍然对主线程的局部变量无影响;也就是说线程间的ThreadLocal是单独的副本,所以就有线程安全的说法。