Java基础知识25--ThreadLocal使用详解
1.ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量;这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
ThreadLocal实例通常来说都是private static类型的,它们希望将状态与线程进行关联。这种变量在线程的生命周期内起作用,可以减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
2.ThreadLocal 方法详解及原理分析
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。因此,ThreadLocal中可以持有任何类型的对象。
整个ThreadLocal类中核心内容都是对ThreadLocalMap进行操作,而ThreadLocalMap的核心内容都是围绕Entry组成的Map存储结构进行操作。关于ThreadLocal、ThreadLocalMap、Entry之间的关系如图所示:
Thread类中维护了两个关于ThreadLocalMap的成员变量。
// ThreadLocal变量 ThreadLocal.ThreadLocalMap threadLocals = null; // InheritableThreadLocal变量,该类继承自ThreadLocal ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
threadLocals在Thread类中作为成员变量,初始化线程对象时并不会被赋予值,只有在使用ThreadLocal时进行赋值。查看ThreadLocal中的get方法。
ThreadLocal 的几个方法: ThreadLocal 可以存储任何类型的变量对象, get返回的是一个Object对象,但是我们可以通过泛型来制定存储对象的类型。
public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本 public void set(T value) { } //set()用来设置当前线程中变量的副本 public void remove() { } //remove()用来移除当前线程中变量的副本 protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的
2.1 ThreadLocal的set()方法
public void set(T value) { //1、获取当前线程 Thread t = Thread.currentThread(); //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空, //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else // 初始化thradLocalMap 并赋值 createMap(t, value); }
从上面的代码可以看出,ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。
2.2 ThreadLocal的get方法
public T get() { // 获取当前操作线程 Thread t = Thread.currentThread(); // 调用getMap方法,返回当前线程的实例变量threadLocals值 ThreadLocalMap map = getMap(t); // 如果返回map不为空,返回map中所存储的以当前ThreadLocal对象为key的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 如果map为空进行map值的初始化 return setInitialValue(); } ThreadLocalMap getMap(Thread t) { // 返回传入线程(当前线程)中成员变量的threadLocals值 return t.threadLocals; } private T setInitialValue() { // 调用initialValue()方法设置初始值,默认不设置任何值,可以在创建ThreadLocal // 对象时被重写进行初始化,只会进行一次初始化。 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } void createMap(Thread t, T firstValue) { // 初始化当前线程对象实例变量threadLocals的值,Map所对应的key为当前ThreadLocal对象 t.threadLocals = new ThreadLocalMap(this, firstValue); }
下面用一张图从宏观上,认识一下ThreadLocal的整体结构:
从上图中看出,在每个Thread类中,都有一个ThreadLocalMap的成员变量,该变量包含了一个Entry数组,该数组真正保存了ThreadLocal类set的数据。
Entry是由threadLocal和value组成,其中threadLocal对象是弱引用,在GC的时候,会被自动回收。而value就是ThreadLocal类set的数据。
2.3 总结
Thread对象中通过维护了一个ThreadLocal.ThreadLocalMap类型的threadLocals变量实现线程间变量隔离,并维护了一个ThreadLocal.ThreadLocalMap类型的inheritableThreadLocals变量实现线程间变量的继承,是否继承由线程初始化时inheritThreadLocals参数进行决定,默认不继承。
ThreadLocal中核心存储的类为ThreadLocalMap类,ThreadLocalMap类本身是一个定制化的Map,这个Map以当前ThreadLocal对象作为key值进行K-V存储。ThreadLocalMap的初始化容量为16,扩容因子为2/3。
ThreadLocalMap在进行存储时,会获取当前this对象的threadLocalHashCode值(这也是为什么使用ThreadLocal作为key的原因),该值是唯一的,只在ThreadLocalMap中有用,使用Unsafe提供的AtomicInt类操作获取。
ThreadLocalMap中进行存储的基本单位为Entry数组,数组下标通过threadLocalHashCode进行&运算并根据当前数组长度进行自动扩容。
3 ThreadLocal 常见使用场景
ThreadLocal 适用于如下两种场景
1、每个线程需要有自己单独的实例
2、实例需要在多个方法中共享,但不希望被多线程共享
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
package com.ttbank.flep.core.test1; /** * @Author lucky * @Date 2022/7/5 16:34 */ public class ThreadLocalTest { static class MyThread extends Thread { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); @Override public void run() { super.run(); for (int i = 0; i < 3; i++) { threadLocal.set(i); System.out.println(getName() + " threadLocal.get() = " + threadLocal.get()); } } } public static void main(String[] args) { MyThread myThreadA = new MyThread(); myThreadA.setName("ThreadA"); MyThread myThreadB = new MyThread(); myThreadB.setName("ThreadB"); myThreadA.start(); myThreadB.start(); } }
运行结果(注意:结果不唯一):
参考文献:https://blog.csdn.net/u010445301/article/details/111322569
https://www.zhihu.com/question/477411483
https://www.cnblogs.com/starsray/p/16220037.html