多线程之ThreadLocal理解、应用及源码分析

简介:         一、引入:为什么要使用ThreadLocal         之前,在某位大牛的一篇文章中,看到了实现线程安全的几个层次,分别是:         1、使用无状态的对象               无状态对象也就是不变的对象,它是最安全的,因此不需要考虑线程间同步等安全...

        一、引入:为什么要使用ThreadLocal

        之前,在某位大牛的一篇文章中,看到了实现线程安全的几个层次,分别是:

        1、使用无状态的对象

              无状态对象也就是不变的对象,它是最安全的,因此不需要考虑线程间同步等安全性问题;

        2、做到线程封闭

              线程封闭就是把对象封装到单个线程里,只有这一个线程才能看到该对象;

        3、采用同步技术

              比如采用synchronized关键字,锁定某个类、某个方法或者某个代码块,或者使用互斥锁、读写锁等锁技术,最根本的就是让某个对象,或者某个方法,或者某个代码块,在某一时刻,始终只能有一个线程操纵。

        对于第一个使用无状态的对象,它的应用场景非常有限,而大型复杂系统,往往需要大量有状态的对象;而对于第三个采用同步技术,它的实现比较复杂,需要考虑线程间同步问题,稍有不慎,就会写出错误的代码,带来毁灭性的打击,并且,线程间同步技术需要使用锁等,使得很多线程可能处于等待状态,会带来性能方面的消耗。

        我们今天单说下线程封闭。何谓线程封闭?线程封闭就是把对象封装到单个线程里,只有这一个线程才能看到该对象。它可以通过以下三种方式实现:

        1、ad-hoc线程封闭;

        2、栈封闭;

        3、使用ThreadLocal。

        ad-hoc线程封闭就是完全靠实现者控制的线程封闭,这个我们今天不说。而栈封闭虽然概念显得高大上,其实就一句话,多使用局部变量。为什么呢?这就要从JVM的运行时内存模型说起,局部变量的引用是保持在线程栈中的,只对当前线程可见,其他线程不可见,所以说局部变量是线程安全的。而第三种方式--使用ThreadLocal,那么问题来了,ThreadLocal是什么,它又是如何实现线程封闭的呢?

        二、入题:什么是ThreadLocal

        ThreadLocal有一个名称,叫做线程本地变量。它为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。由此可知,它采用了一种空间换时间的策略,来保证线程间的安全性。它实际上也是程序控制线程封闭的一种机制,不过这种机制是Java自身提供的罢了。

        三、理解:ThreadLocal内部是如何实现的?

        既然ThreadLocal为变量在每个线程中都创建了一个副本,那么我们先来看下它是如何存取的。在此之间,我们先介绍下线程Thread类中一个十分重要的成员变量threadLocals,其定义如下:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
        它是一个ThreadLocal.ThreadLocalMap类型的对象,也就是ThreadLocal内部类ThreadLocalMap的实例。而ThreadLocalMap是ThreadLocal的一个静态内部类,也就是说,它不需要依靠外部类的实例化而实例化。这在 《答群友问:Java静态内部类、普通内部类等的理解》一文中已经提到过了。那么ThreadLocalMap是什么呢?里面存储的是什么东西呢?先来看下它的构造方法,如下:

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        实际上,ThreadLocalMap就是一种Map结构的数据类型。它的Key为ThreadLocal实例,而value则是任意一个Object。而ThreadLocalMap中存储key-value的是其内部类Entry,定义如下:

        /**
         * 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;
            }
        }
        它继承自WeakReference,是对ThreadLocal实例的一种弱引用。而弱引用的作用其实很简单,当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。

        好了,言归正传。我们还是先说下ThreadLocal是如何为变量在每个线程中都创建了一个副本,也就是它的存取方法。

        先看存,存是依靠ThreadLocal的set()方法实现的,代码如下:

    /**
     * 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) {
    	
    	// 获取当前线程t
        Thread t = Thread.currentThread();
        
        
        // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map
        // ThreadLocalMap中存储的是ThreadLocal实例到value的映射,
        // value就是我们想在线程中持有的变量副本
        ThreadLocalMap map = getMap(t);
        
        if (map != null)// 如果map存在
        	// 将当前ThreadLocal实例this与传入的value映射关系放入map
            map.set(this, value);
        else// 如果map不存在
        	
        	// 调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值
            createMap(t, value);
    }
        逻辑比较简单,大体流程如下:

        1、获取当前线程t;

        2、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:

              ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;

        3、判断map是否存在:

              3.1、如果map存在,将当前ThreadLocal实例this与传入的value映射关系放入map;

              3.2、如果map不存在,调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值。

        我们先看下getMap()方法,代码如下:

    /**
     * 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;
    }
        getMap()方法其实获取就是上面我们提到过的对应线程t的threadLocals变量。

        接下来,我们再看下createMap()方法,代码如下:

    /**
     * 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
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        createMap()方法就是构造一个ThreadLocalMap实例,赋值给线程t的threadLocals变量。而构造ThreadLocalMap实例时,传入的key为当前ThreadLocal实例this,value则是我们想在线程中持有的变量副本。

        存数据讲完了,我们再看下取数据。而取数据则是通过ThreadLocal的get()方法实现的,代码如下:

    /**
     * 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() {
    	
    	// 获得当前线程t
        Thread t = Thread.currentThread();
        
        // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map
        // ThreadLocalMap中存储的是ThreadLocal实例到value的映射,
        // value就是我们想在线程中持有的变量副本
        ThreadLocalMap map = getMap(t);
        
        // 如果map不为空
        if (map != null) {
        	
        	// 利用当前ThreadLocal实例this,从map中获取对应的条目Entry,即e,
        	// Entry的key就是ThreadLocal实例this,value对应为实际需要的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            
            // 如果e不为空,直接返回e中的value,并转化为T类型
            if (e != null)
                return (T)e.value;
        }
        
        // 如果map为空,调用setInitialValue()返回value
        return setInitialValue();
    }
        取数据的get()方法也比较简单,大体逻辑如下:

        1、获得当前线程t;

        2、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:

              ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;

        3、判断map是否为空:

              3.1、如果map不为空,利用当前ThreadLocal实例this,从map中获取对应的条目Entry,即e,Entry的key就是ThreadLocal实例this,value对应为实际需要的value;如果e不为空,直接返回e中的value,并转化为T类型;

              3.2、如果map为空,调用setInitialValue()返回value。

        getMap()方法我们在上面已经讲解过了,这里我们只看下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() {
    	
    	// 调用initialValue()初始化value,空方法,返回的为null,可以根据需要进行重写
        T value = initialValue();
        
        // 获取当前线程t
        Thread t = Thread.currentThread();
        
        // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map
        // ThreadLocalMap中存储的是ThreadLocal实例到value的映射,
        // value就是我们想在线程中持有的变量副本
        ThreadLocalMap map = getMap(t);
        
        if (map != null)// 如果map存在
        	// 将当前ThreadLocal实例this与初始化的value放入map
            map.set(this, value);
        else// 如果map不存在
        	
        	// 调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值
            createMap(t, value);
        return value;
    }
        setInitialValue()方法,说白了,就是设置初始化的value值。它的处理流程如下:

        1、调用initialValue()初始化value,initialValue是一个空方法,返回的为null,可以根据需要进行重写;

        2、获取当前线程t;

        3、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:

              ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;

        4、判断map是否存在:

              4.1、如果map存在:将当前ThreadLocal实例this与初始化的value放入map;

              4.2、如果map不存在:调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值;

        5、返回value。

        其中,createMap()方法上面已经介绍过了,这里就不再赘述了。



相关文章
|
3月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
53 2
|
2月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
63 0
|
3月前
|
Java 编译器
创建线程方式及应用总结
创建线程方式及应用总结
29 0
|
18天前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
|
19天前
|
Java 开发者
Java中的多线程基础与应用
【9月更文挑战第22天】在Java的世界中,多线程是一块基石,它支撑着现代并发编程的大厦。本文将深入浅出地介绍Java中多线程的基本概念、创建方法以及常见的应用场景,帮助读者理解并掌握这一核心技术。
|
22天前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
55 5
|
1月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
28天前
|
Java 调度 开发者
Java中的多线程基础及其应用
【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。
|
2月前
|
Java 数据库
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】
这篇文章通过一个电商商品详情页的实战案例,展示了如何使用`CompletableFuture`进行异步编排,以解决在不同数据库表中查询商品信息的问题,并提供了详细的代码实现和遇到问题(如图片未显示)的解决方案。
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。

热门文章

最新文章