你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

ThreadLocal

2021/10/24 16:28:48

1. ThreadLocal简述

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。

如果想实现每一个线程都有自己的专属本地变量该如何解决呢? 

在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。

在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。

可以使用 get() 和 set()方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

举个栗子:

比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。


2. ThreadLocal栗子

举个栗子:

public class ThreadLocalDemo implements Runnable {

	private static final ThreadLocal<SimpleDateFormat> FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
		SimpleDateFormat yyyyMMdd_hHmm = new SimpleDateFormat("yyyyMMdd HHmm");
		return yyyyMMdd_hHmm;
	});

	public static void main(String[] args) throws InterruptedException {
		ThreadLocalDemo obj = new ThreadLocalDemo();
		for (int i = 0; i < 10; i++) {
			Thread t = new Thread(obj, "" + i);
			Thread.sleep(new Random().nextInt(1000));
			t.start();
		}
	}

	@Override
	public void run() {
		System.out.println("Thread Name= " + Thread.currentThread().getName() + "; default Formatter = " + FORMAT_THREAD_LOCAL.get().toPattern());
		try {
			Thread.sleep(new Random().nextInt(1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//formatter pattern is changed here by thread, but it won't reflect to other threads
		FORMAT_THREAD_LOCAL.set(new SimpleDateFormat());
		System.out.println("Thread Name= " + Thread.currentThread().getName() + " formatter = " + FORMAT_THREAD_LOCAL.get().toPattern());
	}
}

测试结果:  

 Thread-0 已经改变了 formatter 的值,但仍然是 thread-2 默认格式化程序与初始化值相同,其他线程也一样。


3. ThreadLocal源码分析

Thread类源码:

public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
}

从上面Thread类 源代码可以看出Thread类中有一个threadLocals和 一个inyertableThreadLocals变量;

它们都是 ThreadLocalMap类型的变量,可以把 ThreadLocalMap理解为ThreadLocal类实现的定制化的HashMap。

默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal类的set或get方法时才创建它;

实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。

ThreadLocal的set方法

public void set(T value) {
    //获取当前线程实例
    Thread t = Thread.currentThread();
    //以实例t为key,获取对应的线程的集合对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
     else
        createMap(t, value);
}

 分析上面set方法代码:

  • 最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上;
  • ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
  • ThreadLocal 类中可以通过Thread。currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap 对象。

ThreadLocalMap是ThreadLocal的静态内部类,每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

进入ThreadLocalMap这个类看一下: 

如下图,比如我们在同一个线程中声明了两个 ThreadLocal对象的话,会使用 Thread内部都是使用仅有那个ThreadLocalMap存放数据的,ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal对象调用set方法设置的值。

ThreadLocal数据结构