ThreadLocal简单分析与实现原理

《Java并发编程之美》读书笔记

ThreadLocal

多线程在访问同一个共享变量的时候容易出现并发的问题,特别是在多个线程对同一个共享变量进行写入的时候,一般都要对共享变量进行适当的同步。
同步的措施一般都是加锁,这就需要使用者对锁有一定的了解,这显然增加了使用者的负担,那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢->ThreadLocal
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal,那么访问这个变量的每个线程都会有这和个变量本地的一个副本。当多个线程操作这个ThreadLocal变量时,实际上是在操作自己本地内存里面的变量,从而避免了线程安全问题。创建了一个ThreadLocal变量后,每个线程都会复制一个变量复制到自己的本地内存

ThreadLocal使用示例

public class ThreadLocalTest {
    static void print(String str){
    //获取到当前线程本地内存中的localVariable值
        System.out.println(str+":"+localVariable.get());
        //删除当前线程本地内存中的localVariable值
        localVariable.remove();
    }
    //创建ThreadLocal变量
    static ThreadLocal localVariable=new ThreadLocal<>();

    public static void main(String[] args) {
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
    //设置当前线程本地内存中的localVariable值
                localVariable.set("threadOne local variable");
                print("threadOne");
                System.out.println("threadOne remove after"+":"+localVariable.get());
            }
        });
        Thread threadTwo=new Thread(new Runnable() {
            @Override
            public void run() {
                localVariable.set("threadTwo local variable");
                print("threadTwo");
                System.out.println("threadTwo remove after"+":"+localVariable.get());
            }
        });
        threadOne.start();
        threadTwo.start();
    }
}

本例子开启了两个线程,在每个线程内部设置了本地变量的值,然后调用print函数打印当前本地变量的值,如果打印后调用了本地变量的remove方法之后,则会删除本地内存中的共享变量。


ThreadLocal简单分析与实现原理_第1张图片

线程One run方法通过设置localvariable的值,这其实是设置的是线程one本地内存中的一个副本,这个副本线程two是访问不了的。

ThreadLocal实现原理

Thread类内部会有一个threadLocals和inheritableThreadLocals,他们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的hashMap,在默认的情况下,每个线程中的两个变量都为null,只有当前线程第一次调用ThreadLocal的set或者get方法之后才会创建他们,其实每个线程的本地变量并不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals里面,也就是说ThreadLocal类型的本地变量存放在具体的线程的内存空间中,ThreadLocal就是一个空壳,它通过set方法把value值放入线程的threadlocals里面存放起来,当调用线程使用它的get方法时,再从当前线程的threadlocals变量里面将其拿出来,如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadlocals里面,所以当不需要使用本地变量的时候,可以通过调用ThreadLocal变量里面的remove方法,从当前线程的threadlocals里面删除该本地变量
另外 Thread里面的threadlocals为什么设置为map结构?很明显是因为多个线程可以关联多个ThreadLocal变量。

ThreadLocal简单分析与实现原理_第2张图片

简单分析ThreadLocal的set,get以及remove方法的是实现逻辑

  1. public void set(T value)
 public void set(T value) {
        //获取到当前线程
        Thread t = Thread.currentThread();
        //将当前线程作为key去找对应的线程变量,找到则设置
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
        //如果是第一次调用就创建当前线程对应的HashMap
            createMap(t, value);
        }
    }

代码中首先获取调用线程,然后使用当前线程作为参数调用getMap(t)方法


ThreadLocal简单分析与实现原理_第3张图片

可以看到,getMap(t)的作用是获取线程自己的变量threadlocals,threadlocals变量被绑定到了线程的成员变量上。
如果getMap(t)返回值不为空,则把value值设置到threadLocals中,也就是把变量值放入到当前线程的内存变量threadLocals中。threadLocals是一个HashMap结构,其中的key就是当前ThreadLocal的实例对象的引用,value是通过set方法传递的值
如果getMap(t)返回空值则说明是第一次调用set方法,这时用 createMap(t, value)创建当前线程的threadLocals变量。

ThreadLocal简单分析与实现原理_第4张图片

createMap创建当前线程的threadLocals变量。

  1. T get()
  public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量 是一个ThreadLocalMap结构
        ThreadLocalMap map = getMap(t);
        //如果threadLocals不为空,则返回对应本地变量的值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //threadLocals为空的话就初始化当前线程的threadLocals变量
        return setInitialValue();
    }

上诉代码首先获取当前线程的实例,在获取当前线程的threadLocals变量,如果不为null则直接返回当前线程绑定的本地变量,否则执行代码初始化。

private T setInitialValue() {
        //初始化为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //如果当前线程的threadLocals变量不为空
        if (map != null) {
        //value为null
            map.set(this, value);
        } else {
          //如果当前线程的threadLocals变量为空
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal) this);
        }
        return value;
    }

如果当前线程的threadLocals变量不为空,则设置为当前线程的本地变量值为null,否则调用createMap(t, value)方法创建当前线程的threadLocals变量。

  1. void remove()


    ThreadLocal简单分析与实现原理_第5张图片

如果当前线程的threadLocals变量不为空,则删除当前线程中指定ThreadLocal实例即this的本地变量。
总结
在每个线程内部都有一个名为threadLocals的成员变量,这个变量的类型是HashMap,其中key就为我们定义的ThreadLocal类型的变量的this引用,value则为我们用set方法设置的值,每个线程的本地变量存放在线程自己的内存变量threadLocals里面,如果当前线程一直不消亡,那么这些本地变量就会一直存在,所以可能会造成内存溢出,因此使用完毕后记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。
注:在JUC包里面的ThreadLocalRandom,就是借鉴这中思想实现的。

ThreadLocal简单分析与实现原理_第6张图片

ThreadLocal不支持继承性

public class ThreadLocalDemo {
    //创建线程变量
    private static ThreadLocal threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set("helloworld");
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread:"+threadLocal.get());
            }
        });
        thread.start();
        System.out.println("main:"+threadLocal.get());
    }
}

也就是说,同一个ThreadLocal变量在父线程中设置值后,在子线程中是获取不到的,如之前所说,这是很正常的现象,因为子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程所以子线程访问时为null;

Inheritable ThreadLocal类

为了解决ThreadLocal类不支持继承性的这个问题,InheritableThreadLocal应运而生,它继承自ThreadLocal,提供了一个属性,就是让子线程可以访问可以访问在父线程中设置的本地变量,
InheritableThreadLocal源代码:

public class InheritableThreadLocal extends ThreadLocal {
  
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

由以上代码可知,InheritableThreadLocal继承ThreadLocal类,并且重写了三个方法。重写了createMap方法,所以现在第一次调用set方法的时候,创建的是当前线程的t.inheritableThreadLocals变量的实例而不再是threadLocals实例。当调用get方法获取当前线程内部的map变量时,获取的是t.inheritableThreadLocals而不再是threadLocals。
综上,在InheritableThreadLocal世界里,变量由inheritableThreadLocals代替了threadLocals

观察如何让子线程可以访问父线程的本地变量。

  public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        //获取当前线程
        Thread parent = currentThread();
        //如果父线程的inheritThreadLocals不为null
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //设置子线程中的inheritThreadLocals变量
            this.inheritableThreadLocals =
           ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
        this.tid = nextThreadID();
    }

如上的代码在创建的过程中,在构造函数中会调用私有的构造方法,先获取当前的线程,这里是main函数所在的线程,也就是main线程,然后再判断main函数所在的线程里面inheritableThreadLocals是否为空,然后就会执行createInheritedMap方法


可以看到createInheritedMap的内部使用的是父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量。

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal key = (ThreadLocal) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
 
 

在该构造函数的内部将父线程的inheritableThreadLocals成员变量复制到新的ThreadLocalMap变量当中
总结,InheritableThreadLocal通过重写ThreadLocal类的代码让本地变量保存到了具体的inheritableThreadLocals里面,那么线程在通过InheritableThreadLocal实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals里面
把之前的代码改为:

 private static ThreadLocal threadLocal=new InheritableThreadLocal<>();
ThreadLocal简单分析与实现原理_第7张图片

可见,现在可以从子线程正常获取到线程变量的值了。
在什么情况下需要子线程可以获取到父线程的thredLocal变量呢?
比如子线程需要使用存放在threadLocal变量中的用户信息,再比如说一些中间件需要把统一的id追踪的整个调用链路记录下来。其实子线程使用父线程中的threadLocal方法有很多种,比如创建线程时候,使用父线程中的变量,并将其复制到子线程中,或者在父线程中构造一个map作为参数传递给子线程。但是这些方法都改变了我们的使用习惯,所以在这些情况下InheritableThreadLocal就显得比较有用。

参考资料:
《Java并发编程之美》

你可能感兴趣的:(ThreadLocal简单分析与实现原理)