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数组的维护比较复杂,以后有机会再来研究补充。

posted @ 2015-08-06 17:05  CQUMonk  阅读(962)  评论(0编辑  收藏  举报