zoukankan      html  css  js  c++  java
  • ThreadLocal源码分析

        我们知道,进程是OS分配资源的最小单位,而线程是执行操作的最小单位并不享有资源。ThreadLocal实现了线程数据变量的本地存储,每个线程都存储自己的变量,所有的线程都使用同样的ThreadLocal<T>对象来存取变量,但是每个线程在存取时看到的变量值是不同的,不会影响到其他线程的变量,并且值可以为null。

    整个实现架构图如下:

    image

        一个线程可以持有多个ThreadLocal对象,每个ThreadLocal实例其实很轻量级,只保存hashCounter分配给它的hash值和自身的一个弱引用。存取时只要将values里的obj数组复制当前方法的局部变量中操作就可以了。

        原来的JDK中,table是用map实现的;在1.7源码中使用了object数组来存放<ThreadLocal,T>,如图间隔存放了kv;这样做避开了线程并发的锁操作,大大加快了存取的速度。另外值得提一点的是,hashCounter每次分配给ThreadLocal对象的hash值都是偶数,这样取得的index位置来存放ThreadLocal对象,index+1位置存放变量值,十分巧妙。

    ThreadLocal类结构如下,我们接下来依次分析这些方法:

    image

    • Values    静态内部类,相当于一个全局变量,里面维护了一个object数组,使每个线程都可以访问它。
    • public ThreadLocal() {}构造方法为空
    • public T get()    返回了当前线程中存储的变量值
    • protected T initialValue()    返回null,根据需要重写该方法
    • public void set(T value)    设置当前线程中存储的变量值
    • public void remove()    删除当前线程中存储的变量值
    • Values initializeValues(Thread current)   创建当前进程对应的values对象
    • Values values(Thread current)    获取当前进程的values实例

        介绍完了整个架构,我们先不去看values这个静态内部类,其实它维护了ThreadLocal到变量值的映射,一个hashtable而已,回头再来看它。

    先来看一眼完成当前线程变量值的get方法:

    public T get() {
            // Optimized for the fast path.
            Thread currentThread = Thread.currentThread();//获取到当前线程实例
            Values values = values(currentThread);//获取到当前线程对应的values实例
            if (values != null) {
                Object[] table = values.table;
                int index = hash & values.mask;
                if (this.reference == table[index]) {
                    return (T) table[index + 1];
                }
            } else {
                values = initializeValues(currentThread);//如果当前线程对应的values为空,就新建一个
            }
    
            return (T) values.getAfterMiss(this);
        }

        方法中,根据当前线程实例获取到values,先来看看如果values为空会如何?

        如果values为空则对其初始化,调用initializeValues方法:

    /**
         * Creates Values instance for this thread and variable type.
         */
        Values initializeValues(Thread current) {
            return current.localValues = new Values();//new一个values实例
        }

        我们来看一下Values的构造方法:

    Values() {
                initializeTable(INITIAL_SIZE);//INITIAL_SIZE为16
                this.size = 0;//table中存储的键值对entry数目
                this.tombstones = 0;//废弃的entry数目
            }

        通过initializeTable方法来创建一个object数组,容量为32,mask值为0x1F。

    private void initializeTable(int capacity) {
                this.table = new Object[capacity * 2];//通过给定的初始化容量创建table,一个obj数组
                this.mask = table.length - 1;//之前capacity规定必须为2的幂,这里length默认为31,
                this.clean = 0;
                this.maximumLoad = capacity * 2 / 3; // 2/3 最大负载因子
            }

        我们重新回到get方法中,方法最后返回getAfterMiss(this),该方法将当前ThreadLocal传入,并返回initialValue()定义的值,这个方法是可以自定义重写的。

        如果values不为空,我们将副本table复制到当前方法变量中进行操作,由于每个ThreadLocal对象都有固定的hash值,所以不存在线程并发的问题。

        ThreadLocal中其他的操作方法也是这样。操作完成后,我们需要与Values中的数组交互,这里就调用了put方法:

    /**
             * Sets entry for given ThreadLocal to given value, creating an
             * entry if necessary.
             */
            void put(ThreadLocal<?> key, Object value) {
                cleanUp();//先清理了废弃的元素
    
                // Keep track of first tombstone. That's where we want to go back
                // and add an entry if necessary.
                int firstTombstone = -1;
    
                for (int index = key.hash & mask;; index = next(index)) {
                    Object k = table[index];
    
                    if (k == key.reference) {
                        // Replace existing entry.
                        table[index + 1] = value;
                        return;
                    }
    
                    if (k == null) {
                        if (firstTombstone == -1) {
                            // Fill in null slot.
                            table[index] = key.reference;
                            table[index + 1] = value;
                            size++;
                            return;
                        }
    
                        // Go back and replace first tombstone.
                        table[firstTombstone] = key.reference;
                        table[firstTombstone + 1] = value;
                        tombstones--;
                        size++;
                        return;
                    }
    
                    // Remember first tombstone.
                    if (firstTombstone == -1 && k == TOMBSTONE) {
                        firstTombstone = index;
                    }
                }
            }

        每次操作object数组,都要先清理一下废弃的元素。然后再进行元素存放。

    总结

        ThreadLocal与values的组合设计实现了多个线程存储本地变量而又互不干扰的功能,更令人叫绝的是通过固定hash值分配的方式,避开了锁操作。关于Values内部的object数组的维护比较复杂,以后有机会再来研究补充。

  • 相关阅读:
    org.eclipse.swt.SWTException: Invalid thread access问题解决方法
    V3700系列存储数据恢复成功
    导致磁盘阵列数据丢失的7个常见原因/早做准备哦
    服务器分区丢失数据恢复过程(阵列数据恢复)
    EFS加密文件无法打开怎么办
    raid5硬盘硬件修复;条带分析方法;阵列重组
    程序员节/技术党福利:ORACLE 环境故障数据恢复方案
    HP MSA存储 raid组lvm下vxfs文件系统数据恢复方案
    如何排除服务器中RAID5故障/服务器数据恢复案例
    linux服务器数据恢复方法_服务器硬盘故障解决方案
  • 原文地址:https://www.cnblogs.com/cqumonk/p/4708451.html
Copyright © 2011-2022 走看看