ThreadLocal本地线程变量

ThreadLocal

1.ThreadLocal的作用

通常情况下,我们创建的变量可以被任何一个线程访问并修改,但是在多线程运行的环境下,我们希望每一个线程都有自己的本地专属变量,该怎么实现呐?(例如每个线程都保存该线程独有的 UserId 、TranactionId)

类似于JVM中分配内存,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区TLAB(Thread Local Allocation Buffer),提升对象分配的效率

Java的JDK提供了一个解决方案: ThreadLocal

2.ThreadLocal的使用

Web应用程序就是典型的多任务引用,Java提供线程池可以方便执行这些请求任务并复用线程;

每个用户的请求,都会创建一个任务 Runnable

Runnable task = ()->{
  public void UserPorcess(User user){
    checkPermisson();
    loadResouce();
    doWork();
    saveStatus();
    sendResponse();
  }
}
// 放到线程池中运行
executor.execute(task);

传入的用户参数 User user ,如何在一个线程内传递状态?

  • 1.将user参数传入到所有方法中
  • 2.将user信息保存到本地线程中,需要用户信息执行get()方法获取上来

Context上下文:

在一个线程中,横跨若干个方法调用,需要传递的对象,通常称之为上下文(Context),它是一种状态。

对于第一种方法,将user参数传入所有的方法中,且不论每个方法都增加一个context参数的复杂程度,一旦调用第三方库时,无法修改代码,user参数就无法传入。

TheadLocal就是将信息保存包本地线程中,在一个线程中传递同一个对象

 This class provides thread-local variables.  These variables differ from
 their normal counterparts in that each thread that accesses one (via its
 get or set method has its own, independently initialized copy of the variable. 
 ThreadLocal instances are typically private static fields in classes that wish 
 to associate state with a thread (e.g. a user ID or Transaction ID).

适用场景:

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

存储用户session的例子

// 私有静态
private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws Exception {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (Exception ex) {
            throw new Exception(ex);
        }
        return s;
}

3.ThreadLocal的实现

3.1ThreadLocal概览

一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map - ThreadLocalMap

 /*
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 */
public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    protected T initialValue() {
        return null;
    }
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    public 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) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Returns {@code true} if there is a value in the current thread's copy of
     * this thread-local variable, even if that values is {@code null}.
     *
     * @return {@code true} if current thread has associated value in this
     *         thread-local variable; {@code false} if not
     */
    boolean isPresent() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        return map != null && map.getEntry(this) != null;
    }

    /**
     * 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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    /**
     * 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.set(this, value);
        } else {
            createMap(t, value);
        }
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

set及get方法流程

  • set
  • get

每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别的线程没办法拿到,从而实现了隔离。

public class Thread implements Runnable {
 
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

3.2ThreadLocalMap

ThreadLocalMap时ThreadLocal的静态内部类

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

3.3ThreadLocal带来的问题-内存泄漏

WeakReference是弱引用,弱引用的对象下一次GC的时候一定会被回收(不管内存是否足够)

static class Entry extends WeakReference<ThreadLocal<?>>

但是value是强引用

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

Entry对象中的value就有可能一直得不到回收,发生内存泄露

解决方案:

当前线程的finally语句中调用remove()方法

public void remove() {
      ThreadLocalMap m = getMap(Thread.currentThread());
       if (m != null) {
            m.remove(this);
       }
}

4.总结

4.1应用场景

  • Web应用很多场景的cookie,session等数据隔离都是通过ThreadLocal去做实现的
  • Spring框架的 TransactionSynchronizationManager
  • SimpleDataFormat多线程安全问题解决

4.2使用事项

  • 为防止内存泄漏,需要最后finally语句中调用remove()方法

引用

ThreadLocal-廖雪峰

Java面试必问:ThreadLocal终极篇

不要用狭隘的眼光看待不了解的事物,自己没有涉及到的领域不要急于否定. 每天学习一点,努力过好平凡的生活.
原文地址:https://www.cnblogs.com/GeekDanny/p/14907321.html