`

ThreadLocal工作原理

    博客分类:
  • Java
 
阅读更多

概述



 
 

翻译过来的大概意思是: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之后就会变得线程安全,因为每个线程都会有一个对应的实例副本。

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

 

  • 大小: 37.8 KB
分享到:
评论

相关推荐

    深入理解 Java 之 ThreadLocal 工作原理1

    ThreadLocal是Java中用于线程局部变量的一个工具类,它的工作原理主要体现在如何在不同的线程之间隔离变量的副本,确保每个线程拥有自己的独立数据。这个设计模式在多线程编程中尤其有用,因为它避免了传统的同步...

    深入理解ThreadLocal工作原理及使用示例

    深入理解ThreadLocal工作原理及使用示例 ThreadLocal是Java提供的一种解决多线程程序并发问题的工具类,自JDK1.2版本以来提供了java.lang.ThreadLocal类。ThreadLocal的主要作用是为每个使用该变量的线程提供独立的...

    JAVA ThreadLocal类深入

    ### 一、ThreadLocal工作原理 ThreadLocal通过内部的Map存储每个线程的变量副本。当线程首次调用get()方法时,如果没有对应的副本,ThreadLocal会调用initialValue()方法初始化副本。这个方法默认返回null,但通常...

    ThreadLocal的原理,源码深度分析及使用.docx

    ThreadLocal 的原理、源码深度分析及使用 ThreadLocal 是 Java 语言中的一种机制,用于实现线程本地存储,能够帮助开发者在多线程环境下解决变量访问安全的问题。下面将对 ThreadLocal 的原理、实现机制、使用方法...

    ThreadLocal,你真的了解吗?

    ThreadLocal 工作原理: - 每个 ThreadLocal 实例都有一个 ThreadLocalMap,它不是 HashMap 而是基于 Entry 实现的。 - Entry 使用弱引用作为键,这样当 ThreadLocal 变量没有其他强引用时,Entry 会被垃圾回收,...

    ThreadLocal原理及在多层架构中的应用

    ThreadLocal的工作原理主要基于以下几点: - **内部类ThreadLocalMap**:ThreadLocal在每个线程内部维护了一个名为ThreadLocalMap的哈希映射表,这个表的键是ThreadLocal对象,值是线程局部变量的实际值。这样,每...

    Java并发编程中ThreadLocal的原理与应用分析

    其他说明:为了更好地理解ThreadLocal的工作机制,建议实际动手尝试文中提供的实例代码,同时注意不同版本JDK之间的差异可能会导致部分细节有所变化。对于追求高性能并发应用开发的技术人员而言,了解和运用...

    ThreadLocal的原理是什么,使用场景有哪些.md

    ThreadLocal的原理是什么,使用场景有哪些.md

    ThreadLocal原理及在多层架构中的应用.pdf

    首先,ThreadLocal原理是基于每个线程创建一个私有的数据存储结构(ThreadLocalMap),使得线程可以拥有自己独立的数据副本,而不会与其他线程共享。这意味着,即使多个线程访问相同的变量,它们也各自拥有这个变量...

    ThreadLocal

    在Java中,ThreadLocal的工作原理是为每个线程创建一个单独的存储空间,每个线程可以独立地读写这个变量,而不会影响其他线程。当我们创建一个新的ThreadLocal实例时,它并不会立即分配内存,而是等到线程第一次调用...

    ThreadLocal应用示例及理解

    ### 理解ThreadLocal原理 ThreadLocal内部通过一个ThreadLocalMap来存储每个线程的副本。这个Map的键是ThreadLocal实例,值是线程的局部变量。每个线程都有自己的ThreadLocalMap,存储在Thread类的成员变量中。 ##...

    jKill#basic-notes#ThreadLocal实现原理1

    通过ThreadLocal对象定位到线程:Thread.currentThread()通过ThreadLocal对象拿到所在的ThreadLocalMap: T

    Android 中 ThreadLocal使用示例

    下面将详细介绍ThreadLocal的工作原理、使用方法以及在Android中的实际应用。 ### 1. ThreadLocal工作原理 ThreadLocal内部实现了一个HashMap,用于存储每个线程与对应的变量副本之间的映射关系。当我们创建一个新...

    Java 并发编程之ThreadLocal详解及实例

    下面将详细探讨 ThreadLocal 的工作原理、使用方法以及适用场景。 1. **ThreadLocal 工作原理** - 每个 `ThreadLocal` 实例在每个线程中都有一个对应的存储槽,这个存储槽存储的是变量的副本。当创建一个新的 `...

    ThreadLocal原理分析图,visio软件打开

    ThreadLocal原理分析图,visio软件打开

    java 简单的ThreadLocal示例

    因此,理解ThreadLocal的工作原理并谨慎使用是非常重要的。 在提供的"ThreadLocal示例"压缩包中,可能包含了一些具体的代码示例,展示如何在实际项目中运用ThreadLocal。通过查看这些示例,你可以更深入地理解...

    ThreadLocal_ThreadLocal源码分析_

    ThreadLocal的工作原理基于Java的Thread类。在ThreadLocal中,每个实例都有一个内部Map,这个Map存储了键值对,键是ThreadLocal实例,值就是线程本地的变量副本。在JDK 8之前,这个内部Map是`ThreadLocal....

    ThreadLocal 内存泄露的实例分析1

    at 中专门为每一个 web 应用...理解 `ThreadLocal` 的工作原理以及它如何与类加载器交互,是避免此类问题的关键。在实际开发中,应当养成良好的编程习惯,如使用后及时清理 `ThreadLocal` 变量,以防止内存资源的浪费。

Global site tag (gtag.js) - Google Analytics