ThreadLocal详解

1.这个类是干嘛用的

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object,but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values.

  以上是源代码中对该类的注释,我翻译一下:它能够保存一系列thread定义的值,一个ThreadLocal可以对应多个thread,多个thread可以共享一个Threadlocal,在ThreadLocal中一个thread修改其保存的值并不影响其他的thread的值。
  下面我们来根据它具体的源代码来分析其作用。

2.它有什么功能

  这个就有意思了,我们从源代码中看看它定义了哪些方法就能知道了,不过从1中我们大概可以猜出来它有保存数据的功能,那么就有存取数据的功能,以下我们从源代码中开始分析:

  • 获取值的功能
 /**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
  public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        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);
        }

        return (T) values.getAfterMiss(this);
    }

我们大概浏览一下这个方法,先是获取当前的线程,从这个线程中取出Values对象,在Values对象不为空的情况下在values.table数组中与出其下标,如果当前的ThreadLocal弱引用对象和table数组中指定下标的对象相等,就返回index+1下标的table对象;当values对象为空时初始化values对象,然后再返回table中的对象。
这里我们有几个问题总结下:
  1.Values对象是做什么用的
  2.与出来的下标是怎么与出来的,为什么要通过与一个hash值来定义下标
  3.为什么返回的对象是table数组的后一位
  4.table数组是不是装的ThreadLocal对象
  5.返回的是什么对象
  6.values.getAfterMiss(this)是怎么实现的

下面我们逐一解析:

1.Values对象

1.要了解一个类是做何用的看其注释最为直观,我们先看看它的注释:

/**
* Per-thread map of ThreadLocal instances to values.
*/
static class Values {…

注释的意思就是:每个线程的ThreadLocal实例和值组成的键值对,说实话我这种强行翻译有点拗口,但是意思已经到位了,看完后文你就明白这句注释的意思了。

2.我们再看看在ThreadLocal中是如何调用到的:

Values values = values(currentThread);
-----------------------------------------
 /**
     * Gets Values instance for this thread and variable type.
     */
Values values(Thread current) {
        return current.localValues;     
    }

  从以上代码可以看出Values对象是在当前线程中定义的。
而ThreadLocal类中的set方法为当前线程保存了TheadLocalvalue值,如下所示:

   /**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *为当前线程设置它要存储的变量,如果这个变量为null值,那么该对象还是会被存储下来
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

  这里用到了两个Values类中的方法,在第13行代码中实际是初始化了Values对象,而在第15行代码中则是将要存储的变量存储到Values对象中去。
上面的代码我们才是真正地进入到了Values类,而它其中的变量很重要(后面就会知道为什么了,因为确实用到很多),我们先来认识一下其中的变量:

/**
         * Size must always be a power of 2.
         *table的size
         */
        private static final int INITIAL_SIZE = 16;

        /**
         * Placeholder for deleted entries.
         *已被垃圾回收的对象的占位符
         */
        private static final Object TOMBSTONE = new Object();

        /**
         * Map entries. Contains alternating keys (ThreadLocal) and values.
         * The length is always a power of 2.
         * 存储键值对用的table数组,就是value.put所存的地方
         */
        private Object[] table;

        /** Used to turn hashes into indices. 
        *用来与出table[]下标的数,其值为mask = table.length-1
        */
        private int mask;

        /** Number of live entries. 
        *未被回收的键值对数量
        */
        private int size;

        /** Number of tombstones. 
        *已被垃圾回收的数量
        */
        private int tombstones;

        /** Maximum number of live entries and tombstones. 
        *
        */
        private int maximumLoad;

        /** Points to the next cell to clean up. 
        *开始清除的下标位置
        */
        private int clean;

  了解了它的变量,回到之前的代码,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;
                }
            }
        }

  这里先调用了cleanUp()方法,这个方法是为了记录哪些ThreadLocal对象已经被垃圾回收器回收,然后记录挂了的ThreadLocal数量和现存的ThreadLocal数量以及垃圾回收到的位置:tombstones++size--clean=index;然后在第12行开始无限循环,我们看到table[]数组的偶数位存放的是ThreadLocal的弱引用,或者为null,或者为TOMBSTONE;而奇数位存放的就是Object value,也就是我们要保存的值;在15行,当key为ThreadLocal的引用时,直接覆盖其对应的;而后面两个if()判断语句就是当key为null或者TOMBSTONE时,直接将ThreadLocal.referencevalue赋值给该table[index]、table[index+1],所以我认为这个方法其实可以直接改写成如下代码更为直观:

  void put(ThreadLocal<?> key, Object value) {
           ...
            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) {
                    // Fill in null slot.
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
                    return;
                }

                if (k == TOMBSTONE) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
                    tombstones--;
                    return;
                }
            }
        }

  以上就是对put()方法的分析。
  cleanUp()源码及分析:

 /**
         * Cleans up after garbage-collected thread locals.
         *当ThreadLocal对象被垃圾回收之后,需要将其键值对置为TOMBSTONE和null
         */
        private void cleanUp() {
            if (rehash()) {
                // If we rehashed, we needn't clean up (clean up happens as
                // a side effect).
                return;
            }

            if (size == 0) {
                // No live entries == nothing to clean.
                return;
            }

            // Clean log(table.length) entries picking up where we left off
            // last time.
            int index = clean;
            Object[] table = this.table;
            for (int counter = table.length; counter > 0; counter >>= 1,
                    index = next(index)) {
                Object k = table[index];

                if (k == TOMBSTONE || k == null) {
                    continue; // on to next entry
                }

                // The table can only contain null, tombstones and references.
                @SuppressWarnings("unchecked")
                Reference<ThreadLocal<?>> reference
                        = (Reference<ThreadLocal<?>>) k;
                if (reference.get() == null) {
                    // This thread local was reclaimed by the garbage collector.
                    table[index] = TOMBSTONE;
                    table[index + 1] = null;
                    tombstones++;
                    size--;
                }
            }

            // Point cursor to next index.
            clean = index;
        }

  这里第5行先调用了rehash()方法,然后就是判断table[]中是否有保存ThreadLocal实例,没有就直接退出该方法,然后就是循环,将ThreadLocal实例的弱引用被垃圾回收掉之后将其标志为TOMSTONE和null,同时将成员变量tombstones++,size--,clean=index。看到这里知道了这个方法再结合put()方法我们知道了,这个方法主要就是为了将已被垃圾回收的ThreadLocal对象所保存的位置清空以备后续重新置入键值对。第22行的next()方法也让我学到了[很多][1]:

 /**
         * Gets the next index. If we're at the end of the table, we wrap back
         * around to 0.
         *当index大于table.length时返回的下标将从0开始
         */
        private int next(int index) {
            return (index + 2) & mask;
        }

通过next()方法源码就可以改写其循环为:

for(int counter = table.length;counter>0;counter>>1,index+=2){
    ...
}

这个循环其实是不完整循环,为什么说它是不完整循环呢,因为counter>0,counter>>1限定了其循环次数,假如该table的长度为32,那么其最多循环的次数为6次,而其可存储的键值对的数量则为16对,显然无法循环完全,所以cleanUp()方法里的循环是无法完整遍历的,这里为什么用不完整循环我还是不明白,如果有选手了解希望指点一下。
  rehash()源码及解析:

/**
         * Rehashes the table, expanding or contracting it as necessary.
         * Gets rid of tombstones. Returns true if a rehash occurred.
         * We must rehash every time we fill a null slot; we depend on the
         * presence of null slots to end searches (otherwise, we'll infinitely
         * loop).
         */
        private boolean rehash() {
            if (tombstones + size < maximumLoad) {
                return false;
            }

            int capacity = table.length >> 1;

            // Default to the same capacity. This will create a table of the
            // same size and move over the live entries, analogous to a
            // garbage collection. This should only happen if you churn a
            // bunch of thread local garbage (removing and reinserting
            // the same thread locals over and over will overwrite tombstones
            // and not fill up the table).
            int newCapacity = capacity;

            if (size > (capacity >> 1)) {
                // More than 1/2 filled w/ live entries.
                // Double size.
                newCapacity = capacity * 2;
            }

            Object[] oldTable = this.table;

            // Allocate new table.
            initializeTable(newCapacity);

            // We won't have any tombstones after this.
            this.tombstones = 0;

            // If we have no live entries, we can quit here.
            if (size == 0) {
                return true;
            }

            // Move over entries.
            for (int i = oldTable.length - 2; i >= 0; i -= 2) {
                Object k = oldTable[i];
                if (k == null || k == TOMBSTONE) {
                    // Skip this entry.
                    continue;
                }

                // The table can only contain null, tombstones and references.
                @SuppressWarnings("unchecked")
                Reference<ThreadLocal<?>> reference
                        = (Reference<ThreadLocal<?>>) k;
                ThreadLocal<?> key = reference.get();
                if (key != null) {
                    // Entry is still live. Move it over.
                    add(key, oldTable[i + 1]);
                } else {
                    // The key was reclaimed.
                    size--;
                }
            }

            return true;
        }

该方法主要作用为,当tombstones + size > maximumLoad时,table[]数组的存储空间不够大了,需要扩展其length,就要重新初始化table[]数组和一些成员变量,所以调用到第32行代码初始化,而在这之前保存下当前table[]中已存储的变量Object[] oldTable = this.table;,然后再通过从43行起的循环开始将旧table[]中的值赋值给新的table[]数组。
  以上我们就已经分析完了Value类的存数据时所用的方法,回到一开始的ThreadLocal获取其保存值的方法第23行中用到了Values类中的getAfterMiss()方法,这个从方法名就可以看出是找不到该ThreadLocal作为键时用的方法。
  下面来看下getAfterMiss()方法:

        /**
         * Gets value for given ThreadLocal after not finding it in the first
         * slot.
         *当第一个index下标中没有查找到该ThreadLocal的reference时,调用此方法将ThreadLocal的reference作为key值保存,该方法在ThreadLocal类中的get()方法中被调用
         */
        Object getAfterMiss(ThreadLocal<?> key) {
            Object[] table = this.table;
            int index = key.hash & mask;

            // If the first slot is empty, the search is over.
            if (table[index] == null) {
                Object value = key.initialValue();

                // If the table is still the same and the slot is still empty...
                if (this.table == table && table[index] == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                }

                // The table changed during initialValue().
                put(key, value);
                return value;
            }

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            // Continue search.
            for (index = next(index);; index = next(index)) {
                Object reference = table[index];
                if (reference == key.reference) {
                    return table[index + 1];
                }

                // If no entry was found...
                if (reference == null) {
                    Object value = key.initialValue();

                    // If the table is still the same...
                    if (this.table == table) {
                        // If we passed a tombstone and that slot still
                        // contains a tombstone...
                        if (firstTombstone > -1
                                && table[firstTombstone] == TOMBSTONE) {
                            table[firstTombstone] = key.reference;
                            table[firstTombstone + 1] = value;
                            tombstones--;
                            size++;

                            // No need to clean up here. We aren't filling
                            // in a null slot.
                            return value;
                        }

                        // If this slot is still empty...
                        if (table[index] == null) {
                            table[index] = key.reference;
                            table[index + 1] = value;
                            size++;

                            cleanUp();
                            return value;
                        }
                    }

                    // The table changed during initialValue().
                    put(key, value);
                    return value;
                }

                if (firstTombstone == -1 && reference == TOMBSTONE) {
                    // Keep track of this tombstone so we can overwrite it.
                    firstTombstone = index;
                }
            }
        }

第11行的key.initialValue();

 /**
     * Provides the initial value of this variable for the current thread.
     * The default implementation returns {@code null}.
     *用户可以继承`ThreadLocal`类然后重写该方法
     * @return the initial value of the variable.
     */
    protected T initialValue() {
        return null;
    }

  getAfterMiss方法做的任务跟put方法差不多,只不过一个是取,一个是存了,操作的内容都是table数组的变量修改和成员变量的赋值。
  总结:Values这个内部类就是为了当前线程在ThreadLocal中存储变量所用,那么这里又有一个问题了,为什么不直接用HashMap存储变量呢?如果有选手知道麻烦帮我解答一下。

2.与出来的下标是怎么与出来的,为什么要通过与一个hash值来定义下标

我们先看下在ThreadLocal中定义的hash字段值是什么:

/**
     * Internal hash. We deliberately don't bother with #hashCode().
     * Hashes must be even. This ensures that the result of
     * (hash & (table.length - 1)) points to a key and not a value.
     *
     * We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
     * every other bucket) to help prevent clustering.
     */
    private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

  这里的hashCounterAutomaticInteger类对象,它是线程安全的对象,它通过构造方法初始化成员变量value为初始值,并通过getAndAdd()方法传入增长步长来实现自动增长,后面我会出一篇关于AutomaticInteger类的解析,这里先简单放一下它的源码调用:

 /** Hash counter. */
    private static AtomicInteger hashCounter = new AtomicInteger(0);
    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }

 /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    }

       /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
     /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

  ThreadLocal中定义的变量hash是一个“魔数”,好像跟斐波拉契数列有关,这个原理我不懂,但是我也做了测试看Doug Lea’s Magic Number博客里的测试,它不是从0开始的循环,通过与mask的相与将会得到一个偶数index。

3.为什么返回的对象是table数组的后一位

  因为table数组的偶数位存放的是ThreadLocal的弱引用,在偶数位+1的奇数位存放当前thread的变量。他们以数组前后作为一个键值对来存放。

4.table数组是不是装的ThreadLocal对象

  是的,而且还有当前thread的变量

5.返回的是什么对象

  当前thread所存储的对象。

6.values.getAfterMiss(this)是怎么实现的

  看Values类的解析
  

3.为什么需要这个类在Looper类中定义

  1.保证了一个线程中只有一个Looper对象

4.未完成

1.AtomicInteger源码解析
2.了解ThreadLocal的其他应用
3.总结逻辑运算

参考文章:
https://www.jianshu.com/p/dde3db5abfa7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值