ThreadLocal 源代码分析

  在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。本文将对ThreadLocal源代码分析
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">        源代码为android sdk  19.如有不对或者错误的望指出。</span>
         首先在TheadLocal 中有一个重要的变量来保存与相关的值。在API 19 中这个变量叫Value。请看源码:
<span style="background-color: rgb(255, 255, 255);">    /**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;</span>
 等下我们来详细分析这个类。现在先回到ThreadLocal 两个经常用到的两个方法:set(T value)和get()。由浅入深,先分析get():
    
    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    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);
    }
 这个程序大概的意思我想大家都能看懂,通过源代码发现,查找值是通过value.table这个数组通过索引值来找到值得。其实value.table 数组存取键值对是这样一种情况:如果键值的key的索引为index,则所对应到的value索引为index+1.  由此分析可知  hash&values.mask 获取的就是key的索引值。values.mask=values.table.length-1;从源代码注释也可以看到
   /**
     * 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);
  在上面几行代码中,我们隐约的发现了:key值并不是ThreadLocal 本身,而是reference 这个变量。我们继续来看源码:
    /** Weak reference to this thread local instance. */
    private final Reference<ThreadLocal<T>> reference
            = new WeakReference<ThreadLocal<T>>(this);
发现这是ThreadLocal的弱引用。这是为什么呢?因为开启线程的开销比较大,所以我们经常会把线程放入线程池中来对线程进行重用。如果不采用弱引用的方式,那线程自身所带的数据都无法释放。这会造成内存紧张,容易发生OOM。
接下来分析 set(T value)
  
<pre name="code" class="java">

* * @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); }
   程序比较简单,重点来分析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;
                }
            }
        } 
  大致意思比较简单:通过hash 值定位到key的索引值,然后判断这个索引值是否有对应的TheadLocal;如果有,则返回value。如果没有,通过找到firstTombstone这个索引值,然后赋值对应的key和value。在这里有人要问了这个firstTombstone是什么意思?别着急,慢慢分析。接下我们分析cleanup(),根据字面意思它是清理内存的作用,让我们来看源代码:
   
  /**
         * Cleans up after garbage-collected thread locals.
         */
        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;
        }
  从代码可以知道,ThreadLocal 对象被回收了,但是它的弱引用所对应的value却还存在,通过value=null来释放value所占用的内存。并给它设置一个标志用TOMBSTONE。表示这块内存已经被释放了。
  这样上面 firstTombstone 这个字段的含义也一目了然了。
  到这里我们思考一下,既然使用数组来保存对象,数组肯定要扩容啊,没错,确实有这个方法,叫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;
        }
   就是当前正在使用的内存大于分配内存的1/2的时候进行数扩容。
   还有最后一个方法就是remove了,用来释放内存。
<pre name="code" class="java">        /**
         * Removes entry for the given ThreadLocal.
         */
        void remove(ThreadLocal<?> key) {
            cleanUp();

            for (int index = key.hash & mask;; index = next(index)) {
                Object reference = table[index];

                if (reference == key.reference) {
                    // Success!
                    table[index] = TOMBSTONE;
                    table[index + 1] = null;
                    tombstones++;
                    size--;
                    return;
                }

                if (reference == null) {
                    // No entry found.
                    return;
                }
            }
        }
 
  通过代码分析也可以知道,也用TOMBSTONE来表示这块内存已经被释放掉了,有兴趣的朋友可以仔细研究代码。
  最后分析下本篇文章最长用到的一个函数:
  <pre name="code" class="java">        /**
         * Gets the next index. If we're at the end of the table, we wrap back
         * around to 0.
         */
        private int next(int index) {
            return (index + 2) & mask;
        }
 
这方法的好处是可以循环遍历,嗯 ,以后借鉴这种方法也是不错的。 

本人最后提醒一下:1  使用ThreadLocal 要真正实行线程之间数据无干扰的话,必须要进行对象深拷贝。
                                    2   使用ThreadLocal   要记得在恰当的时机remove
      
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值