java面试之java多线程=》面试完美回答ThreadLocal

遇到ThreadLocal怎么办?

前言
面试的时候经常会被问道ThreadLocal,哦吼,这是个啥?每次咱们只能回答ThreadLocal似乎可以将数据复制一份放到线程中以供线程使用,然后,嗯哼,就没了,可咋整,是不是很尴尬,这个时候面试官就会认为你还是知道那么点皮毛的,接着他就会问,那你知道是怎么保证每个线程都只取自己的数据吗?父线程能够拿到子线程的ThreadLocal数据吗?你平时都是使用在什么场景。。。这个时候可不是完全懵逼吗?呃。。。呃。。。不好意思我不会,那一首凉凉送给自己。

常问问题

  • 你知道ThreadLocal吗?
  • ThreadLocal你是怎么理解的?
  • ThreadLocal使用在什么场景?
  • ThreadLocal会存在内存泄露吗?为什么?

知识讲解

  1. 什么是ThreadLocal?
/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code 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).
 *
 * 

For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. *

 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(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();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * 
*

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). * * @author Josh Bloch and Doug Lea * @since 1.2 */

先来看一波javadoc文档怎么说:
这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
从这里我们可以看到ThreadLocal是为线程提供一个数据副本,且这个数据是与该线程息息相关,这里也有说,比如用户的id,延伸到用户session和事务id等

  1. ThreadLocal的set、get方法
    /**
     * 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.
     *	设置此线程局部变量的当前线程副本到指定值。大多数子类将不需要
	 *重写此方法,仅依赖{@link#initialValue}(初始化)
	 *方法设置线程局部变量的值
     * @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);
    }
    /**
     * 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.
     *返回当前线程副本中的值
	 *线程局部变量。如果变量没有
	 *当前线程,它首先初始化为返回的值
	 *通过调用{@link#initialValue}方法
     * @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();
    }
  1. ThreadLocal的内存泄漏
    ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。
  2. 使用举例
/**
 * @author 13738
 */
public class ForThreadLocal {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

    public static void main(String[] args) {
        testThreadLocal();
    }

    public static void testThreadLocal() {
        ExecutorService executors = Executors.newFixedThreadPool(5);
        for (int i = 1; i < 5; i++) {
            String j = i + "chai";
            String x = i + "sanRen";
            executors.submit(() -> {
                threadLocal.set(j);
                threadLocal2.set(x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + threadLocal.get());
                System.out.println(Thread.currentThread().getName() + threadLocal2.get());
            });
        }
        threadLocal.remove();
        threadLocal2.remove();
        executors.shutdown();
    }
}

上述代码说明:
1.ThreadLocal中可以存放多个线程的数据,每个线程的数据key为线程id
2.一个线程可以往多个ThreadLocal对象存储数据,但同一个ThreadLocal对象存储多次会被覆盖

二、面试回答

面试回答时我们主要从以下几个方面切入答案:

  1. 说说ThreadLocal是什么?
    首先,ThreadLocal一般称为线程本地变量或者线程本地存储,其内部维护了一个ThreadLocalMap的静态内部类,通过set、get、remove等方法,来操作每个线程私有的ThreadLocalMap变量,以实现线程间数据的隔离。
  2. 讲讲ThreadLocal的应用场景有哪些?
    主要应用场景:
    1.每个线程都需要自己对应的实例
    2.多个方法共享实例,但又不被多个线程使用
    举例:用户session传递,数据库连接,spring事务,日志等
  3. 谈谈使用ThreadLocal的好处以及注意点?
    ThreadLocal的使用既可以保证线程安全,同时能够降低代码耦合度,实现更加优雅的代码,但是要注意ThreadLocal存在内存泄漏的问题,解决是每次使用完以后需要remove方法清除
  4. ThreadLocal的子类InheritableThreadLocal
    由于ThreadLocal线程私有的特性,父线程与子线程之间数据也是无法共享,这是可以通过InheritableThreadLocal来实现。

你可能感兴趣的:(#,java面试,java,多线程,thread,面试)