ThreadLocal工作原理

概述



 
 

翻译过来的大概意思是:ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问

(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。

 


总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个独立的变量副本,提供了保持对象的方法和避免参数传递的复杂性。

 

实现原理

ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法:

  1. void set(Object value) 设置当前线程的线程局部变量的值。
  2. public Object get() 该方法返回当前线程所对应的线程局部变量。
  3. public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  4. protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。

ThreadLocal内部如何为每一个线程维护变量副本呢? 其实在ThreadLocal类中有一个静态内部类ThreadLocalMap,用键值对的形式存储每一个线程的变量副本,其中key为当前ThreadLocal对象,而value对应线程的变量副本。每个线程可能存在多个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(ThreadLocalMap为线程的一个属性)
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        /*
            ThreadLocalMap的key为ThreadLocal对象,value为对应的变量副本
            此处获取当前ThreadLocal对象对应的Entry
         */
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 获取变量副本并返回
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果ThreadLocalMap为空,则初始化
    return setInitialValue();
}

/**
 * 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) {
    /*
        获取当前线程的ThreadLocalMap(若不存在则创建),
        并将value放入ThreadLocalMap中
     */
    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);
 }


/**
 * 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) {

    /* 
       为当前线程创建一个ThreadLocalMap对象并赋值给threadLocals,  
       并存入第一个值(this表示当前ThreadLocal对象)
     */
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


/**
 * 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
    T value = initialValue();
    /*
        获取当前线程的ThreadLocalMap(若不存在则创建),
        并将value放入ThreadLocalMap中
     */
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

   

 

 

上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类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.
 *
 * map中的每个节点Entry,其键key是ThreadLocal并且还是弱引用,这也导致了后续会产生内存泄漏问题的原因
 */
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.
 *
 * 初始化容量为16,以为对其扩充也必须是2的指数
 */
private static final int INITIAL_CAPACITY = 16;

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 *
 * 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。
 */
private Entry[] table;

// ...... 其他操作和Map类似

 

 

总之,为不同线程创建不同的ThreadLocalMap,以线程本身为区分点,每个线程之间其实没有任何的联系。说是存放了变量的副本,其实可以理解成为每个线程单独new了一个对象。

 

 

对象存放在哪里

在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

 

问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?

 

其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见

注意:变量是保存在线程中的,而不是保存在ThreadLocal变量中

 

 

真的只能被一个线程访问么

上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。如下,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

private void testInheritableThreadLocal() {
    final ThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("droidyue.com");
    Thread t = new Thread() {
        @Override
        public void run() {
            super.run();
            Log.i(LOGTAG, "testInheritableThreadLocal = " + threadLocal.get());
        }
    };

    t.start();
}

 

上面的代码输出的日志信息为:

testInheritableThreadLocal = droidyue.com

 

//Thread.java
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //code goes here
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
}

 

上面代码就是在线程创建的时候,复制父线程的inheritableThreadLocals的数据。

 

 

使用场景

实现单个线程单例以及单个线程上下文信息存储,比如交易id等。

实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例副本。

承载一些线程相关的数据,避免在方法中来回传递参数。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值