ThreadLocal实现原理分析

简介: ThreadLocal实现原理分析

大概有一年多的时间没有更新过文章了,要想输出一篇优质的文章需要耗费很多精力。可能是之前太过于懒惰了吧,经过一段精力的消耗,渐渐地失去了一些动力。但是写文章虽然耗时,但是有个好处就是在复习一些知识点的时候,只需要查看之前写的博客,在很短的时间内就能把知识点回想起来。曾经的初中老师总是唠叨说好记性不如烂笔头。看来是“诚不欺我呀!”。希望之后还是能保持一定的更新节奏,把对技术的思考都记录下来。跟大家一起分享知识


概述



今天主要是记录一下ThreadLocal的实现原理。引用下官方文档对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方法)都有自己独立初始化的变量副本。”


我们知道一个变量的作用域大概分为"全局作用域"(static变量)、“类作用域”(成员变量)、“方法作用域”(方法内的局部变量)、“代码块作用域”(代码块的局部变量)。那么ThreadLocal可以为线程提供局部变量,那说明提供的变量的作用域是Thread类作用域。换句话说可以通过ThreadLocal给Thread提供拓展新的成员变量的功能,是不是有点像Kotlin的拓展新方法,新变量的功能有点类似?如果这样描述你觉得对ThreadLocal更容易理解,那你就认为ThreadLocal可以给线程提供新的成员变量,只不过这个成员变量是没有名称的。它的赋值只能通过ThreadLocal对象的set()方法,获取该变量的值只能通过ThreadLocal的get()方法。下面通过一个简单的例子来讲解下ThreadLocal是怎么给Thread提供局部变量的


简单的例子



import java.util.concurrent.TimeUnit;
public class TestThreadLocal {
    //1
    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    public void setThreadValue(String value) {
        //2
        stringThreadLocal.set(value);
    }
    public String getThreadValue() {
        //3
        return stringThreadLocal.get();
    }
    public static void main(String[] args) {
        TestThreadLocal testThreadLocal = new TestThreadLocal();
        //4
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                testThreadLocal.setThreadValue("t1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(testThreadLocal.getThreadValue() + " in thread1");
            }
        });
        //5
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                testThreadLocal.setThreadValue("t2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(testThreadLocal.getThreadValue() + " in thread2");
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序运行的结果是

t1 in thread1
t2 in thread2

注释//1处定义了一个ThreadLocal变量,为线程提供String类型的线程局部变量


注释//2处通过ThreadLocal的set方法为线程局部变量赋值


注释//3处通过ThreadLocal的get方法获取线程局部变量的值


注释//4 //5分别创建线程t1和t2。并且通过ThreadLocal为t1 t2赋值以及获取值


源码分析


在分析源码前我们先来看下ThreadLocal相关的类图

20190626134413220.png

1.ThreadLocal类定义了set和get方法


2.Thread有个名为threadLocals 类型是ThreadLocal.ThreadLocalMap的成员变量。ThreadLocalMap是什么呢。顾名思义肯定是存储键值对的。类似HashMap<K,V>。这里有两个疑问,第一既然是Map接口那么存储的键值对是什么?第二为什么要用ThreadLocalMap而不是HashMap呢?


3.ThreadLocal.ThreadLocalMap类是Map结构,第一key存储的是ThreadLocal<?>对象,value存储的是Thread的局部变量的值,第二map的key-value对应的Entry是一个WeakReference,而且该WeakReference引用的是Key。也就是说当ThreadLocal对象被GC回收了之后,Map对应的Entry也会被回收掉,同时给Thread提供的局部变量也会被回收掉,这样设计不会造成ThreadLocal的内存泄漏,比HashMap<ThreadLocal,T>好


接下来从源码角度分析

//ThreadLocal.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);
    }
//ThreadLocal.getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
//ThreadLocal.ThreadLocalMap.set(ThreadLocal<?> key,Object value)
private void set(ThreadLocal<?> key, Object value) {
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            Entry[] tab = table;
            int len = tab.length;
            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();
        }

ThreadLocal.set(T value)流程如下


获取到当前线程


获取当前线程的ThreadLocalMap对象

如果ThreadLocalMap对象不为空,把键值对set到map中,如果为空创建map并初始化


ThreadLocal.get()方法流程"可猜而知"


获取当前线程

获取当前线程的ThreadLocalMap对象

如果Map对象为空,返回默认值,如果不为空则根据key获取map中的value


那么看下源码来印证下猜测吧

//ThreadLocal.get()
 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();
    }

上述源码,刚好印证了猜想是对的。


最后留下一个问题



既然ThreadLocal为线程提供局部的变量,那么该变量只能在当前线程中赋值和访问。那么真的没有办法在t1中访问和修改t2中的局部变量吗?当然有了通过反射。

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
public class TestThreadLocal {
    ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    public void setThreadValue(String value) {
        stringThreadLocal.set(value);
    }
    public String getThreadValue() {
        return stringThreadLocal.get();
    }
    public static void main(String[] args) {
        TestThreadLocal testThreadLocal = new TestThreadLocal();
        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                testThreadLocal.setThreadValue("t1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(testThreadLocal.getThreadValue() + " in thread1");
                while (true){
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                testThreadLocal.setThreadValue("t2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(testThreadLocal.getThreadValue() + " in thread2");
                try {
                  Field field =  Thread.class.getDeclaredField("threadLocals");
                  field.setAccessible(true);
                  Object map = field.get(t1);
                  Class clazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
                  Method method  = clazz.getDeclaredMethod("getEntry",ThreadLocal.class);
                  method.setAccessible(true);
                  Object entry  = method.invoke(map,testThreadLocal.stringThreadLocal);
                  Class clazz2 = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
                    Field field1 =clazz2.getDeclaredField("value");
                    field1.setAccessible(true);
                    System.out.println(field1.get(entry)+" in thread 2");
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


相关文章
|
16天前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
30 0
|
3月前
|
存储 安全 Java
ThreadLocal原理讲解
ThreadLocal原理讲解
21 0
|
8月前
|
存储 Java
大厂是怎么用ThreadLocal?ThreadLocal核心原理分析
ThreadLocal**是Java中的一个线程本地变量类。它可以让每个线程都有自己独立的变量副本,而不会相互影响。
84 1
|
9月前
|
存储 Java 数据安全/隐私保护
ThreadLocal的实现原理&源码解析
ThreadLocal是Java中的一个线程封闭机制,它提供了一种线程局部变量的解决方案,可以使每个线程都拥有自己独立的变量副本,互不干扰。
67 0
|
9月前
|
存储 SpringCloudAlibaba Java
浅析ThreadLocal使用及实现原理
提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其`get` 或 `set`方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。`ThreadLocal`实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联 。所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以这么说Th
76 0
浅析ThreadLocal使用及实现原理
|
存储 安全 Java
ThreadLocal源码分析
ThreadLocal,即线程局部变量。主要用于线程间数据隔离。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
|
存储 算法 安全
ThreadLocal原理剖析
ThreadLocal原理剖析
163 0
|
存储 算法 Java
ThreadLocal原理解析
ThreadLocal原理解析
78 1
ThreadLocal原理解析
|
Java 定位技术
ThreadLocal原理
经典八股文之ThreadLocal原理
151 0