ThreadLocal 的实现原理和应用场景

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21586317/article/details/81076953

一、ThreadLocal 是什么

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据

可以理解成线程本地变量或线程本地存储,ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量

二、使用方法

示例代码:

public class Page47 {
	
	ThreadLocal<Integer> int_local = new ThreadLocal<>();
	ThreadLocal<String> str_local = new ThreadLocal<>();
	
	private void set() {
		int_local.set(120);
		int_local.set(136);
		str_local.set("Hello");
	}
	
	private void change() {
		int_local.set(150);
		str_local.set("World");
	}
	
	private int getInt() {
		return int_local.get();
	}
	
	private String getStr() {
		return str_local.get();
	}

	public static void main(String[] args) throws InterruptedException {
		Page47 page47 = new Page47();
		// 主线程中设置值
		page47.set();
		// 打印线程名称:main
		System.out.println("当前线程:" + Thread.currentThread().getName());
		// 主线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
		System.out.println(page47.getInt());
		// 主线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
		System.out.println(page47.getStr());
		
		System.out.println("=======");
		
		Thread thread = new Thread() {
			public void run() {
				// 子线程中设置值
				page47.set();
				System.out.println("当前线程:" + Thread.currentThread().getName());
				// 子线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
				System.out.println(page47.getInt());
				// 子线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
				System.out.println(page47.getStr());
				
				// 子线程中改变 ThreadLocal 类型变量 int_local 和 str_local 对应的值
				page47.change();
				System.out.println("子线程修改值后:");
				// 子线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
				System.out.println(page47.getInt());
				// 子线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
				System.out.println(page47.getStr());
			};
		};
		thread.start();
		thread.join();
		
		System.out.println("=======");
		// 打印线程名称
		System.out.println("当前线程:" + Thread.currentThread().getName());
		// 主线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
		System.out.println(page47.getInt());
		// 主线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
		System.out.println(page47.getStr());
	}
	
}

控制台打印结果:
控制台打印结果
从控制台打印结果可以看出:不同线程对 ThreadLocal 类型变量进行修改,只在当前线程有效,不影响其它线程

2.1 问题一:ThreadLocal 怎么存的

ThreadLocal#set():

public void set(T value) {
	// 获取当前线程
	Thread t = Thread.currentThread();
	// 获取当前线程对应的存储(ThreadLocalMap)
	// 关键一:getMap() 返回值为 ThreadLocalMap
	ThreadLocalMap map = getMap(t);
	// map 存储不为空
	if (map != null)
		// 关键二:ThreadLocalMap#set()
		// key 为当前线程的 ThreadLocal
		map.set(this, value);
	else // map 存储为空
		// 创建 ThreadLocalMap
		createMap(t, value);
}

关键一:getMap()

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

由此可知,Thread 的成员变量 threadLocals 是 ThreadLocalMap 类型

关键二:ThreadLocalMap#set()

private void set(ThreadLocal<?> key, Object value) {

	// Entry 是 ThreadLocalMap 的静态内部类
	// Entry 构建函数:Entry(ThreadLocal<?> k, Object v),key 是 ThreadLocal,值是变量副本
	// Entry 组成的数组 tab 用来存储线程对应的变量副本
	Entry[] tab = table;
	int len = tab.length;
    // 数组下标的结果和 ThreadLocal 有关,ThreadLocal 相同,数组下标就相同
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

示例代码中的 set() 方法:

public class Page47 {

	ThreadLocal<Integer> int_local = new ThreadLocal<>();
	ThreadLocal<String> str_local = new ThreadLocal<>();
	
	private void set() {
		// 对 int_local 进行 set 操作
		// int_local  先 set(120)
		int_local.set(120);
		// int_local  再 set(136)
		int_local.set(136);

		// 对 str_local 进行 set 操作
		str_local.set("Hello");
	}
	...
}

对示例代码的三行代码进行 debug,查看数组 tab 的状态

1.根据关键二:ThreadLocalMap#set() 可知,数组下标的结果和 ThreadLocal 有关,ThreadLocal 相同,数组下标就相同,则对 ThreadLocal int_local 进行 set 操作,先存 120,之后的 136 会覆盖 120
2.因为示例代码中 str_local 和 int_local 是两个 ThreadLocal 对象,因此数组下标会不同,会在数组 tab 中按对应下标存储

代码 int_local.set(120); 执行之前,ThreadLocalMap 中数组 tab 的状态:
int_local.set(120) 执行之前,ThreadLocalMap 中 tab 数组状态
如图所示 480 行:准备创建一个新的 Entry 对象,key 是 ThreadLocal,value 是 120

再执行一步后,数组 tab 的状态:
数组 tab 存储 120,下标为 3
如图所示,数组 tab 下标为 3 的值为 120

代码执行 int_local.set(136); 执行之前,数组 tab 的状态:
int_local.set(136) 执行之前,数组 tab 的状态
如图 470 行,因为操作的还是 ThreadLocal int_local,所以此时 key 相同,那么将会把之前存储的 120 更新为即将存储的 136

再执行一步后,数组 tab 的状态:
数组 tab 存储 136,下标为 3
如图所示,数组 tab 下标为 3 的 120,此时变成了 136

代码 str_local.set("Hello"); 执行之后,数组 tab 的状态:
数组 tab 存储字符串 Hello
如图所示,数组 tab 下标为 10 的值为 Hello,下标为 3 的值为 136

2.2 问题二:ThreadLocal 怎么取的

ThreadLocal#get():

public T get() {
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程对应的存储(ThreadLocalMap)
    ThreadLocalMap map = getMap(t);
    // map 存储不为空
    if (map != null) {
    	// this 指的是 ThreadLocal,不是当前线程 t
        ThreadLocalMap.Entry e = map.getEntry(this);
        // Entry 不为空
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // 返回 value
            return result;
        }
    }
    // map 为空,返回 setInitialValue() 的返回值
    // 关键三:ThreadLocal#setInitialValue()
    return setInitialValue();
}

关键三:
ThreadLocal#setInitialValue():

private T setInitialValue() {
	// 关键四:initialValue() 默认返回 null
    T value = initialValue();
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程对应的存储(ThreadLocalMap)
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    // 返回 initialValue() 的返回值
    return value;
}

关键四:initialValue() 默认返回 null
ThreadLocal#initialValue():

protected T initialValue() {
	// 如果没有重写当前方法,则默认返回 null
    return null;
}

三、应用场景

当某些数据是以线程为作用域并且不同线程具有不同的数据副本时,就可以考虑采用 ThreadLocal

Android 中 Handler 对应的 Looper 就使用到了 ThreadLocal,Looper 以线程为作用域,且不同线程具有不同的 Looper

Thread 和 ThreadLocal

猜你喜欢

转载自blog.csdn.net/qq_21586317/article/details/81076953