java并发:线程同步机制之ThreadLocal

一、初识ThreadLocal

ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联线程。

A、当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供了一个独立初始化的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。

 

下图展示了实际工作机制:

解释:

  • 在每个线程内部都有一个名为 threadLocals 的成员变量
  • 该变量的类型为HashMap
  • 其中 key为ThreadLocal变量的this引用
  • value为 set方法设置的值

简而言之:每个线程的本地变量存放在线程自己的内存变量 threadLocals中 

二、详述ThreadlocalMap

其类图如下:

ThreadLocal中的方法以及内部类如下图所示:

 

其主要方法如下:

  • void set(T value)

该方法用来设置当前线程中变量的副本

  • public T get()

该方法用来获取当前线程中变量的副本

  • public void remove()

该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。

需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

  • protected T initialValue()

该方法是一个protected方法,ThreadLocal中的缺省实现直接返回一个null,一般用来重写。

三、示例

生成线程序列号

public class ThreadId {

  private static final AtomicInteger atomicInteger = new AtomicInteger(0);

  private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
      return atomicInteger.getAndIncrement();
    }
  };

  // Returns the current thread's unique ID, assigning it if necessary
  public static int get() {
    return threadId.get();
  }
}

 

四、实现机制

(1)get()方法源码

(2)set()方法源码

(3)remove()方法源码

(4)getMap()、createMap

解读:

从源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程。

当一个线程调用 ThreadLocal 的 set 方法设置变量时,当前线程的 ThreadLocalMap 里会存放一条记录,这条记录的key为ThreadLocal的弱引用,value则为设置的值。

 

Note:

分析:

如果当前线程一直存在且没有调用 ThreadLocal 的 remove 方法,则当前线程的 ThreadLocalMap 变量里面会存在对 ThreadLocal 变量和 value对象的引用,它们是不会被释放的,这就会造成内存泄漏。

实际情况:

ThreadLocalMap 里面的 key 是弱依赖,在 GC 的时候被回收,但是对应的 value还是会造成内存泄漏(即 ThreadLocalMap里面存在 key为 null但 value不为 null 的Entry项)。

ThreadLocalMap 的 set、get方法可以在一些时机下对这些Entry项进行清理,但这是不及时的,也不是每次都会执行,所以在一些情况下还是会发生内存漏,因此建议在使用完毕后及时调用 remove方法。

五、ThreadLocalMap

其类图如下:

ThreadLocalMap的部分源码如下:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

此处重点关注一下ThreadLocalMap中的几个成员变量及方法

(1)private Entry[] table;

table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化

Entry是ThreadLocalMap的一个内部类

(2)set()方法

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 

(3)getEntry()方法

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

 

(4)remove()方法

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 

六、总结

ThreadLocal一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。

ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。

posted @ 2016-04-15 20:50  时空穿越者  阅读(2331)  评论(0编辑  收藏  举报