ThreadLocal

ThreadLocal: 为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突

1. ThreadLocal示例

import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {`
  // 1.使用ThreadLocal保存Connection变量.
  // 推荐和 static 一起使用,static会保证ThreadLocalMap中只有一个Entry, private Entry[] table
  private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
  public static Connection getConnection(){

        // 2.如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,并将其保存到线程本地变量中
        if (connThreadLocal.get() == null) {
            Connection conn = ConnectionManager.getConnection();
            connThreadLocal.set(conn);
              return conn;
        }else{
              // 3.直接返回线程本地变量
            return connThreadLocal.get();
        }
    }
    public void addTopic() {
         // 4.从ThreadLocal中获取线程对应的
         Statement stat = getConnection().createStatement();
    }
}

2. ThreadLocal原理

2.1 内部实现

2.1.1 JDK源码

public class ThreadLocal<T> {
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // map不为空, 将threadLocal和value放入threadLocalMap中
            map.set(this, value);
        else
            // 创建ThreadLocalMap, 将threadLocal和value放入threadLocalMap中
            createMap(t, value);
    }

    /**
     * 通过getMap()方法获取到的是ThreadLocalMap, 定义在Thread中
     * ThreadLocal.ThreadLocalMap threadLocals = null;
     *
     * threadLocals本身保存了当前自己所在线程的所有“局部变量”
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        // 线程t中的threadLocalMap
        return t.threadLocals;
    }

    /**
     * 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);
        // 如果不为空, 则根据this, 即调用这个方法的threadLocal作为key, 找到value, 强转换返回
        if (map != null) {
            // 获取threadLocal在map中的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 返回非空值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // map为空, 初始化ThreadLocalMap
        return setInitialValue();
    }

    /**
     * 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() {
        // 初始值, 实际为null. 所以不set, 直接get会返回null
        T value = initialValue();
        Thread t = Thread.currentThread();
        // 当前线程维护的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // map不为空, 直接将值放入
            map.set(this, value);
        else
            // map为空, 创建map, 并且把值放入
            createMap(t, value);
        return value;
    }

    // 为了让子类覆盖而设计.这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次
    protected T initialValue() {
        return null;
    }

    /**
     * 创建 ThreadLocalMap
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        // 初始化线程t的threadLocalMap(threadLocals)
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
              // map不为空, 将threadLocal作为key(this)的key-value对, 移除
             m.remove(this);
     }
}

2.1.2 线程的回收

/**
 * 在线程退出前, 由系统回调, 进行资源清理
 * This method is called by the system to give a Thread
 * a chance to clean up before it actually exits.
 */
private void exit() {
    if (group != null) {
        group.threadTerminated(this);
        group = null;
    }
    /* Aggressively null out all reference fields: see bug 4006245 */
    target = null;
    // 加速资源清理
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
}

2.1.3 内存泄露问题

使用线程池时,当前线程未必会退出.如果将一些太大的对象设置到ThreadLocal中,可能会使系统出现内存泄露的可能.

ThreadLocal内存泄漏的根源: 由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

java的防护措施 弱引用ThreadLocal不会内存泄漏. 在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value.

解决办法:

  • 及时回收对象,使用ThreadLocal.remove()方法将这个变量移除
  • 手动将threadLocal的变量直为null.

2.2 ThreadLocalMap

Thread类中的ThreadLocalMap的实现使用了弱引用, 弱引用是比强引用弱的多的引用. Java虚拟机在垃圾回收时, 如果发现弱引用, 就会立即回收.

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

    // 构造方法
    ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
        // 初始化数组
        table = new Entry[INITIAL_CAPACITY];
        // 计算索引值, threadLocal的hashCode值 & (容量 -1)
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        // 数组第i个值, 初始化, 即将threadLocal和value放入threadLocalMap中
        table[i] = new Entry(firstKey, firstValue);
        // 当前map的元素个数为1
        size = 1;
        // 设置阈值, 初始容量的2/3
        setThreshold(INITIAL_CAPACITY);
    }

    private Entry getEntry(ThreadLocal key) {
        // 计算索引值
        int i = key.threadLocalHashCode & (table.length - 1);
        // 取出table中第i个元素
        Entry e = table[i];
        if (e != null && e.get() == key)
            // e不为空, 并且e中的key等于传入的key, 这里用"==", 表示是同一个引用
            return e;
        else
            // 未找着key对应的Entry
            return getEntryAfterMiss(key, i, e);
    }

    // 从i开始, 往下找, 找到key对应的entry, 或者遇到下一个e == null, 返回
    private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
        // table的副本
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal k = e.get();
            // 找到key对应的entry
            if (k == key)
                return e;
            // k为null,
            if (k == null)
                expungeStaleEntry(i);
            else
                // 取下一个索引值
                i = nextIndex(i, len);
            e = tab[i];
        }
        // 未找到key对应的entry, 返回null
        return null;
    }

    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.

        // table的副本
        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();

            // 找到key, 设置value
            if (k == key) {
                e.value = value;
                return;
            }

            // k为null, 替换
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

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

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

2.3 ThreadLocal的作用

2.3.1 与Thread同步机制的比较

相同点: ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题.

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量.这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大.

而ThreadLocal则从另一个角度来解决多线程的并发访问.ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突.因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了.ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal.

对于多线程资源共享的问题,同步机制采用了"以时间换空间"的方式:访问串行化,对象共享化. 而ThreadLocal采用了"以空间换时间"的方式:访问并行化,对象独享化. 前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响.

总结: ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题.在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性.

2.3.2 Spring使用ThreadLocal解决线程安全问题

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用 域.就是因为Spring对一些Bean(如RequestContextHolder、 TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用 ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了.

2.4 ThreadLocal常见问题

1.threadLocal与InheritableThreadLocal的区别 在ThreadLocal中保持的值的可见范围只在当前线程中有效,那么如果需要在子线程中获取到父线程中的值的话,需要依赖于InheritableThreadLocal

InheritableThreadLocal类继承于ThreadLocal类,其特殊性在于InheritableThreadLocal变量值会传递给所有子线程,这样子线程能获取到父线程的值, 特性: 1:如果一个子线程调用 InheritableThreadLocal 的 get() ,那么它将与它的父线程看到同一个对象 可见性特点:

  • 如果ThreadLocal存储的是不可变对象,则子线程先get,再set修改对象的值,不影响父线程
  • 如果ThreadLocal存储的是可变对象,子线程set新值,不影响父线程
  • 如果ThreadLocal存储的是可变对象,子线程先get对象,再set修改对象的值,影响父线程

2.threadLocal如何避免hash冲突 ThreadLoacal采用AtomicInteger加HASH_INCREMENT并采用开地址法来解决hash冲突

/**
 * 神奇的魔数: This number represents the golden ratio (sqrt(5)-1) times two to the power of 31. The result is then a golden number, either 2654435769 or -1640531527.
 * 让 hash code 能更好地分布在尺寸为 2 的 N 次方的数组里
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 */
private static final int HASH_INCREMENT = 0x61c88647;

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

http://blog.51cto.com/zhangjunhd/53092 https://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable

results matching ""

    No results matching ""