ThreadLocal源码分析

ThreadLocal和线程同步机制相比:都是为了解决多线程中相同变量的访问冲突问题。
  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的。
  ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。ThreadLocal提供了线程安全的共享相同名称的不同对象。
  在一个村中有100户人家,当大家要使用村里唯一的一台拖拉机时,需要使用同步机制。当每家骑自家的自行车时,使用ThreadLocal,虽然大家骑的都是自行车,但是是不同的自行车实例。

Paste_Image.png
Paste_Image.png

package com.hfbank.biz.service;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;

/**

  • 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.

  • @see java.lang.Thread

  • @author Bob Lee

  • jxy 为了更好的使用,1.5增加了泛型的支持,可以看出为了能够更好更简单的被使用,不放过需要优化的任何地方

  • Values中为什么不把:private Object[] table;设置为泛型?

  • 一个ThreadLocal只能保持一个Object,如果想保持多个应如何处理?
    */
    public class ThreadLocal<T> {

    /* Thanks to Josh Bloch and Doug Lea for code reviews and impl advice. */
    // jxy 已经多次看到此类注释,代码如人生

    /**

    • Creates a new thread-local variable.
      */
      public ThreadLocal() {}

    /**

    • 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();
      /
      *

      • jxy values是需要和Thread类结合使用的,并且是一个类似map的存储类
      • 在Thread类中有: java.lang.ThreadLocal.Values localValues;
      • localValues在Thread类中只是定义为包内可见,但没有任何操作,就是为了给ThreadLocal类使用
      • Thread类的实例在运行时,每个线程都是不同的实例,这样localValues也就不会相互干扰
      • 这样两个类配合实现不同线程获取不同实例的需求。
      • 如果不和Thread类配合能否实现现有功能?
      • values为什么做成可以存储多个key-value对?
        */
        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);
      }

    /**

    • Provides the initial value of this variable for the current thread.
    • The default implementation returns {@code null}.
    • @return the initial value of the variable.
    • jxy 该函数是protected类型的,很显然是建议在子类重载该函数的,所以通常该函数都会以匿名内部类的形式被重载,以指定初始值
      */
      protected T initialValue() {
      return null;
      }

    /**

    • 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.
    • @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);
      }

    /**

    • Removes the entry for this variable in the current thread. If this call
    • is followed by a {@link #get()} before a {@link #set},
    • {@code #get()} will call {@link #initialValue()} and create a new
    • entry with the resulting value.
    • @since 1.5
      */
      public void remove() {
      Thread currentThread = Thread.currentThread();
      Values values = values(currentThread);
      if (values != null) {
      values.remove(this);
      }
      }

    /**

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

    /**

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

    /** Weak reference to this thread local instance. */
    // jxy 此处使用WeakReference可以使得当ThreadLocal可以被回收,而不会因为在values中保存有引用而无法回收
    private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);

    /** Hash counter. */
    private static AtomicInteger hashCounter = new AtomicInteger(0);

    /**

    • 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.
    • jxy 通过定义一个static的AtomicInteger类型变量hashCounter,
    • 实现每一个ThreadLocal类实例的hash值不相同。即每次比上一次的值加1.
      */
      private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

    /**

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

      /**

      • Size must always be a power of 2. 2 的幂次方
      • jxy 为什么必须是2的幂次方?
      • 在进行hash计算的时候,是使用:int index = hash & values.mask;
      •                            mask = table.length - 1;
        
      •                            table = new Object[capacity * 2];
        
      • 只有当INITIAL_SIZE为2的幂次方时,mask才可以是类似:00001111的值,在扩充table时也是通过*2来计算capacity的
      • hash每次的值不一样,这样计算出的index(即取hash最后几位的值)就是唯一的。
        */
        private static final int INITIAL_SIZE = 16;

      /**

      • Placeholder for deleted entries.
      • jxy 为什么要定义tombstone?
      • 因为一个线程中可以定义多个ThreadLocal,但是values中的table并不是在定义ThreadLocal时校验是否需要扩充,
      • 当定义量大于capacity时,此时index = hash & values.mask有可能得到重复的值,所以要跟踪哪些entry已经废弃
      • 在put时需要进行tombstone的判断处理。
        */
        private static final Object TOMBSTONE = new Object();

      /**

      • Map entries. Contains alternating keys (ThreadLocal) and values.
      • The length is always a power of 2.
      • jxy table中每一个entry是有三个状态的,null没有进行过设置,tombstone,设置过但是现在废弃了,正常设置状态
        */
        private Object[] table;

      /** Used to turn hashes into indices. */
      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;

      /**

      • Constructs a new, empty instance.
        */
        Values() {
        initializeTable(INITIAL_SIZE);
        this.size = 0;
        this.tombstones = 0;
        }

      /**

      • Used for InheritableThreadLocals.
        */
        Values(Values fromParent) {
        this.table = fromParent.table.clone();
        this.mask = fromParent.mask;
        this.size = fromParent.size;
        this.tombstones = fromParent.tombstones;
        this.maximumLoad = fromParent.maximumLoad;
        this.clean = fromParent.clean;
        inheritValues(fromParent);
        }

      /**

      • Inherits values from a parent thread.
        */
        @SuppressWarnings({"unchecked"})
        private void inheritValues(Values fromParent) {
        // Transfer values from parent to child thread.
        Object[] table = this.table;
        for (int i = table.length - 2; i >= 0; i -= 2) {
        Object k = table[i];

         if (k == null || k == TOMBSTONE) {
             // Skip this entry.
             continue;
         }
        
         // The table can only contain null, tombstones and references.
         Reference<InheritableThreadLocal<?>> reference = (Reference<InheritableThreadLocal<?>>) k;
         // Raw type enables us to pass in an Object below.
         InheritableThreadLocal key = reference.get();
         if (key != null) {
             // Replace value with filtered value.
             // We should just let exceptions bubble out and tank
             // the thread creation
             table[i + 1] = key.childValue(fromParent.table[i + 1]);
         } else {
             // The key was reclaimed.
             table[i] = TOMBSTONE;
             table[i + 1] = null;
             fromParent.table[i] = TOMBSTONE;
             fromParent.table[i + 1] = null;
        
             tombstones++;
             fromParent.tombstones++;
        
             size--;
             fromParent.size--;
         }
        

        }
        }

      /**

      • Creates a new, empty table with the given capacity.
        */
        private void initializeTable(int capacity) {
        this.table = new Object[capacity * 2];
        this.mask = table.length - 1;
        this.clean = 0;
        this.maximumLoad = capacity * 2 / 3; // 2/3
        }

      /**

      • Cleans up after garbage-collected thread locals.

      • jxy 每次在新增或删除entry后需要重新检查table状态,进行更新
        */
        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;
        // jxy 此处没有全部遍历所有的item,猜测是防止性能问题
        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;
        }

      /**

      • 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;
        }

      /**

      • Adds an entry during rehashing. Compared to put(), this method
      • doesn't have to clean up, check for existing entries, account for
      • tombstones, etc.
        */
        void add(ThreadLocal<?> key, Object value) {
        for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];
        if (k == null) {
        table[index] = key.reference;
        table[index + 1] = value;
        return;
        }
        }
        }

      /**

      • 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;

        // jxy 为什么需要循环,和TOMBSTONE定义说明结合
        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;
         }
        

        }
        }

      /**

      • Gets value for given ThreadLocal after not finding it in the first

      • slot.
        */
        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...
         // jxy 此处为什么要增加判断?如果是多线程的话,此类的实例都应该是在一个线程中运行啊
        

// 增加判断是因为上面key.initalValue()的调用,在ThreadLocal中无法预知使用者如何实现initalValue,如果在其中做了诸如put之类的操作有可能导致rehash操作,此时table就会不相等,出现错误
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;
            }
        }
    }

    /**
     * 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;
            }
        }
    }

    /**
     * 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;
    }
}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容