java 线程的工作内存和ThreadLocal

前言

这两者有什么关系吗,一个工作内存,一个ThreadLocal,为什么要合在一起讨论呢,因为工作内存是线程独享的,而ThreadLocal所保存的也是线程独自使用的对象。

1、线程的工作内存

由于计算机的存储设备与处理器的运算速度之间有几个数量级的差距,所以现代计算机系统不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓冲:将运算所需要的数据复制到缓存中,让运算能快速进行,当运算结束后再从高速缓存中同步回内存之中,这样处理器就可以无需等待缓慢的内存读写了。

基于高速缓存很好的解决了处理器与内存的矛盾 ,但是也引入了新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存,如下图所示。当多个处理器的运算任务都涉及到同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,根据这个问题提出的解决方法则是每个处理器在访问缓存时都要遵守一些协议,在读写时要根据协议来进行操作,这些协议有MSI、MESI(Illionis Protocol)、MOSI、Synapse、Firefly及Dragon Protocol等。java虚拟机内存模型中定义的内存访问操作与硬件的缓存访问操作是具有可比性的。除此之外,处理器可能还会对输入的代码进行乱序执行,再将结果重组,以达到与乱序前的结果一致。java虚拟机也有类似的乱序执行优化。
java 线程的工作内存和ThreadLocal_第1张图片

java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,具体关系如下图所示。
java 线程的工作内存和ThreadLocal_第2张图片
这里所说的主内存和工作内存与java内存的java堆、栈、方法区等并不是同一层次的内存划分。如果要比较的话,那么主内存主要对应于java堆中对象实例数据部分,而工作内存则对应虚拟机栈中的那部分区域。从更低的层次来说,主内存就是硬件的内存,而为了获取更好的有运行速度,虚拟机及硬件系统,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。


2、ThreadLocal

在多线程编程时,我们通常使用synchronized和lock来保证操作的原子性和数据的一致性来解决线程安全问题,而导致线程安全问题发生的原因则是多个线程对临界区共享资源的操作,使用这两种方法让线程同步执行,对锁发生争抢时不可避免的会出现有线程要等待的情况,这样的时间效率并不是很好,如果有一种方法能让线程拥有各自独享的资源,则不会发生线程安全的问题,ThreadLocal应运而生。

ThreadLocal的实现原理

    public void set(T value) {
        //1. 先是获取到当前线程
        Thread t = Thread.currentThread();
        //2. 以当前线程为参数,获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3. 若是map不为null,则以当前的ThreadLocal对象为键,value为值存到map中
        if (map != null)
            map.set(this, value);
        else
        //4. 否则创建一个map
            createMap(t, value);
    }

从这个方法看到value是存到ThreadLocalMap里了,先看看这个ThreadLocalMap是怎么来的。

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到是返回了线程t的属性threadLocals,去看看这个threadLocals

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

这里看到threadLocals是Thread的一个实例变量,并且ThreadLocalMap是ThreadLocal的内部类,再回过头来看看若获取的threadLocals为null所执行的代码:

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

可以看到在这里创建了ThreadLocals的实例。

所以set方法的作用就是为当前线程创建一个键为调用set方法的ThreadLocal实例,键为value的ThreadLocalMap的实例或者更新ThreadLocalMap实例的值。

再看get方法:

    public T get() {
    	//1. 获取当前线程
        Thread t = Thread.currentThread();
        //2. 获取map
        ThreadLocalMap map = getMap(t);
        //3. 若map不为null则取值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

从方法中我们可以看到,若是map和entry不为null则取得值:

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

可以看到Entry是从Entry数组中取出来的,数组的作用是当线程有多个ThreadLocal时依然能使用,并通过hash取得索引用于取值。

然后再看get方法中的setInitialValue()做了什么呢?

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

我们看到这里调用了一个没有见过的方法,initialValue():

protected T initialValue() {
        return null;
    }

这是一个受保护方法,实现为ThreadLocal实例赋初始值。

所以get方法的作用是,若取得map和entry不为空则取值并返回,否则返回initialValue()的返回值。

除了存取还有删除:

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

从map中移除以此ThreadLocal实例为键的entry。

参考:

工作内存:
《深入理解java虚拟机》
https://blog.csdn.net/GaryMao123/article/details/79011412
ThreadLocal:
https://blog.csdn.net/qq_38293564/article/details/80459827
https://juejin.im/post/5aeeb22e6fb9a07aa213404a

你可能感兴趣的:(java,源码,读书笔记)