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相关的类图

在这里插入图片描述

  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)流程如下

  1. 获取到当前线程
  2. 获取当前线程的ThreadLocalMap对象
  3. 如果ThreadLocalMap对象不为空,把键值对set到map中,如果为空创建map并初始化

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

  1. 获取当前线程
  2. 获取当前线程的ThreadLocalMap对象
  3. 如果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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值