ThreadLocal

173 阅读5分钟

是什么

顾名思义,线程本地类。

作用

在多线程环境下,每个线程都有一个副本。

如果是普通数据,那么所有线程操作的都是同一份数据,会出现线程同步的问题。

所以,每个线程都有一个副本的好处就是避免线程同步的问题。

应用场景

每个线程都有自己的数据,而不是多线程之间共享这个数据。

1.userId
2.transactionId
3.threadId

具体怎么使用

//jdk7-API官方文档
import java.util.concurrent.atomic.AtomicInteger;

//获取线程id:不是系统默认的线程id,是自定义线程id
 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0); //初始值是0

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() { //匿名对象
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement(); //第一个线程调用的时候,得到的值是1;第二个线程调用的时候,得到的值是2;后面以此类推
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() { //获取当前线程的线程id
         return threadId.get(); //每个线程第一次调用ThreadLocal.get()方法时,因为没有初始值,所以都会调用initialValue()方法,得到一个初始值
     }
 }
 

总结
1.类
自定义工具类

2.类包含了线程本地数据
确保每个线程都有自己的副本数据。//数据属于每个线程自己

3.每个线程获取数据的时候,是通过线程安全的方法去获取数据的 比如,使用并发包-原子类。这样就确保了获取的数据是唯一的。//数据唯一性

底层实现

第一次获取值
第一次调用get()方法获取值的时候,会调用initialValue()方法。所以,一般需要重写initialValue()方法。

//jdk7-ThreadLocal
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) { //第一次调用get(),map为null
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue(); //第一次调用get(),会调用初始化方法initialValue()得到一个初始值
    }
    
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue(); //调用初始化方法,得到初始值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) 
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the <tt>initialValue</tt> method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns <tt>null</tt>; if the
     * programmer desires thread-local variables to have an initial
     * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null; //默认的初始化方法,值是null。所以需要重写。
    }

数据保存在哪里?
有一个数据保存每个线程自己的数据。这个数据使用的数据结构是映射map,jdk自定义了一个新的映射map-ThreadLocalMap。

这个map与其他jdk-map类不同的地方在于,
1.在哪里?
这个map类是在ThreadLocal里面,也就是说,这个数据是与每个线程紧密相关的,只是供内部使用。
2.key?
基于1的原因,导致ThreadLocalMap实现的时候,它的key是什么呢?是线程本身吗?或者换个问法:

为什么不用个全局的map来实现threadlocal,key为当前threalocal+thread,value为其对应的值。毕竟这个实现简单又易于理解。

这里涉及到对象回收的问题。

所以,jdk使用的是哪一种解决方案呢? 1》Thread.map map就是ThreadLocalMap。我们说,每个线程都有自己的数据,为什么这么说呢?原因就在于每个线程都有一个自己的数据ThreadLocalMap。ThreadLocalMap存放了真正的数据value。

2》map(ThreadLocal,value) key是ThreadLocal,而ThreadLocal是final,系统全局唯一,即所有线程使用的是同一个key。同一个key,那怎么找到对应线程自己的数据?虽然key是一样的,但是map不一样,这里说的不一样,是说每个线程都有自己的map,每个线程取数据的时候,也是分2步:1.先取自己线程的map 2.再取map的value。所以哪怕key一样,也没有关系。

/**
     * 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) { //键是线程本身ThreadLocal
                super(k);
                value = v;
            }
        }
        
    
    /**
         * 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();
        }
    
    /**
         * 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);
        }

工作应用

支付


电商

参考

www.jianshu.com/p/9c03c85db…