ThreadLocal详解

Unity Hub报错The Hub as encountered a critical error and must close 如果注册表HKEY_LOCAL_MACHINE\SOFTWARE\Unity Technologies\这个目录下有Hub文件,直接删掉然后重装就可以。C:\Users\Administrator\AppData\Roaming\Unity 作者:SevenVee。HKEY_CURRENT_USER\Software\Unity 下所有项,尝试过,卸载重装,清除临时文件无果后,最终决定删除所有与Hub有关的文件。重装unity后可以打开了。结果:卸载重装后还是会出现此问题。结果:重装后还是无法解决。 阅读详情

使用ThreadLocal的原因

在多线程访问的时候,为了解决线程安全问题,使用 synchronized 关键字来实现线程同步的可以解决多线程并发访问的安全问题,但是在这种解决方案存在有性能问题,多个线程访问到的都是同一份变量的内容,在多线程同时访问的时候每次只允许一个线程读取变量内容,对变量值进行访问或者修改,其他线程只能处于排队等候状态,顺序执行,谁先抢占到系统资源谁先执行,导致系统效率低下。这是一种以延长访问时间来换取线程安全性的策略。简单来说就是以时间长度换取线程安全,在多用户并发访问的时候,由于等待时间太长,这对用户来说是不能接受的。

而使用 ThreadLocal 类,该类在每次实例化创建线程的时候都为每一个线程在本地变量中创建了自己独有的变量副本。每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,那就没有必要使用 synchronized 关键字对这些线程进行同步,它们也能最大限度的使用系统资源,由CPU调度并发执行。并且由于每个线程在访问该变量时,读取和修改的,都是自己独有的那一份变量拷贝副本,不会对其他的任何副本产生影响,并发错误出现的可能也完全消除了。对比前一种方案,这是一种以空间来换取线程安全性的策略。在效率上来说比同步高了很多,可以应对多线程并发访问。

通过查看ThreadLocal类源码,该类中提供了两个主要的方法 get()set(),还有一个用于回收本地变量中的方法remove()也是常用的如下:

下面是 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) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

set() 中通过 getMap(Thread t)方法获取一个和当前线程相关的 ThreadLocalMap,然后将变量的值设置到这个
ThreadLocalMap对象中,如果获取到的 ThreadLocalMap 对象为空,就通过createMap()方法创建。

线程隔离的秘密,就在于 ThreadLocalMap这个类。ThreadLocalMapThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(类似于 Map<K,V> 存储的key-value),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的 ThreadLocalMap 副本,从而实现了变量访问在不同线程中实现隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

来分析源码中出现的getMap和createMap方法的实现:

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

    /**
     * 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) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

通过源码分析可以看出,通过获取和设置 Thread内的 threadLocals变量,而这个变量的类型就是 ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:

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

下面来看一下 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() {
        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();
    }

    /**
     * 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);
        return value;
    }

通过以上源码的分析,在获取和当前线程绑定的值时,ThreadLocalMap对象是以 this 指向的 ThreadLocal 对象为键进行查找的,set() 方法是设置变量的拷贝副本,get() 方法通过键值对的方式获取到这个本地变量的副本的value

remove() 源码分析:
/**
     * 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);
     }

通过源码可以知道,该方法就是通过 this 找到ThreadLocalMap 中保存的变量副本做回收处理。

具体实现例子(实现数据库的连接关闭)

下面看一个使用 ThreadLocal 实现的数据库 Connection 连接:

package cn.czl.util.dbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 为了更加方便的传输关键的引用对象(Connection),所以在本类之中将利用ThreadLocal进行数据库连接的保存
 * 这个类中的ThreadLocal应该作为一个公共的数据,公共的数据使用static声明
 * @author czl
 */
public class DatabaseConnection  {
    private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver" ;
    private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:orcl" ;
    private static final String USER = "scott" ;
    private static final String PASSWORD = "tiger" ;
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>() ;
    /**
     * 取得数据库的连接对象,如果在取得的时候没有进行连接则自动创建新的连接
     * 但是需要防止同一个线程可能重复调用此操作的问题
     * @return
     */
    public static Connection getConnection() {
        Connection conn = threadLocal.get() ;   // 首先判断一下在ThreadLocal里面是否保存有当前连接对象
        if (conn == null) { // 如果此时的连接对象是空,那么就表示没有连接过,则创建一个新的连接
            conn = rebuildConnection() ;    // 创建新的连接
            threadLocal.set(conn);  // 保存到ThreadLocal之中,以便一个线程执行多次数据库的时候使用
        }
        return conn ;   // 返回连接对象
    }
    /**
     * 重新建立新的数据库连接
     * @return Connection接口对象
     */
    private static Connection rebuildConnection() { // 创建新的连接对象
        try {
            Class.forName(DBDRIVER) ;
            return DriverManager.getConnection(DBURL,USER,PASSWORD); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null ;
    }
    /**
     * 关闭数据库连接
     */
    public static void close() {
        System.out.println(Thread.currentThread().getName() + " 关闭数据库连接。");
        Connection conn = threadLocal.get() ;   // 取得数据库连接
        if (conn != null) {
            try {
                conn.close();
                threadLocal.remove();   // 从ThreadLocal中删除掉保存的数据
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

当用户需要调用连接数据库的时候,只需要通过 DatabaseConnection.getConnection()连接数据库,在实现业务逻辑的时候,每次调用都会为调用处创建一个连接数据库的线程副本,每次所作的修改互不干扰,在多用户并发访问使用的时候,很好的避免了使用同步带来的性能问题。

EGO_Plannerv2代码学习(一):栅格建图之V1 V2的区别 最近在尝试将高飞老师的集群规划器部署到真实无人机上跑一下,开这个新帖记录下学习过程中的问题和想法。 阅读详情

相关推荐

FDTD Solutions-入射波长模式

经常在文献中看到仿真光源使用TM/TE模式,这个确定是十分必要的,决定了仿真结果。 但是也不能局限于选择TM/TE,因为不同领域、不同软件之间的使用习惯和命名可能不同。并且在现代光波导器件中,几乎全部 都是混合模式,或者称之为quasi-TE(准TE模式)和quasi-TM(准TM模式),多数情况下都简称为TE模式和TM模式。要知道 到底是TE模式还是TM模式,需要查看其电场的主分量(即振幅...

HECHUANWANG的博客 1万+

深入学习java源码之ThreadLocal.get()()与ThreadLocal.initialValue()

深入学习java源码之ThreadLocal.get()()与 ThreadLocal.initialValue() 并发编程ThreadLocal 线程封闭机制强调局部的概念,就是在写代码的时候,尽量使用局部变量代替全局变量(这种叫做栈封闭),如果一定要使用全局变量,而又想让多个线程之间在访问共享变量的时候互不影响,那就使用ThreadLocal&lt;T&gt;。ThreadLocal&l...

悦分享 2万+

用 SAP XCO XLSX 模块优雅读 Excel:坐标、选择模式与实战案例全解析

SAP ABAP Cloud 中的 XCO XLSX 模块为 Excel 文件处理提供了官方解决方案。文章介绍了三个核心概念:通过 XCO_CP_XLSX=>DOCUMENT 获取文档句柄;使用 CL_XCO_XLSX_COORDINATE 坐标系统定位单元格;利用 Selection Pattern 精确读取数据区域。这些功能在 RAP 批量导入等业务场景中尤为实用,相比传统方案更加简洁可靠。文章还分享了真实项目中的最佳实践,如动态列定位和相对偏移技巧,帮助开发者构建更健壮的 Excel 导入逻辑。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 163

ThreadLocal的get方法的源码分析

今天看面经说,ThreadLocal对象调用get方法,会触发对value内存的释放。所以就阅读源码来验证一下。众所周知,ThreadLocal的实现原理,就是通过在每个线程对象中维护一个map,ThreadLocal每次操作时拿到调用者线程,得到对应map,以自己为key,操作当前线程的资源对象(value)。 其中的map里,维护Entity数组。Entity是一个继承了弱引用的内部类,使用父类构造器将Entity的key设置成了弱引用。 所以说,如果ThreadLocal对象没有其他强引用,那么在gc

weixin_51783482的博客 1670

ThreadLocal

Thread中维护了一个ThreadLocalMap类型的threadLocals,每个entry的key是ThreadLocal对象,value 就是ThreadLocal对象调用set方法设置的值。

suming97的博客 641

ThreadLocal:原理 + get + set

每个线程内都绑定了ThreadLocal,如 class Thread implements Runnable { // 省略了Thread类的其它代码 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;

进击的Coder*的博客 1921

ThreadLocal详解包括Get、Set方法以及,ThreadLocal内存泄漏问题。

我们可以看到此处的set方法,set方法的入参为泛型对应我们上述创建的ThreadLocal时填入的泛型。我们可以看到该类是由泛型修饰的,所以我们在创建的时候可以通过泛型去控制ThreadLocal里面存放的数据类型。这里我定义的泛型为String,我们通过set方法往对应线程里面设置参数。所以它具体的初始化应该是在它的其他方法调用的时候进行初始化的。ThreadLocal的创建是通过New的方式生成的。为C实现的一个本地方法是为了获取当前的线程信息。首先获取到Enrty数组,获取数组的长度。

zzzzzzzzzzzzzzzy的博客 1333

ThreadLocal 详解

总结了ThreadLocal的底层实现原理,分析了内存泄漏的相关问题,以及ThreadLocal的最佳实践。

TheAnnoyingDog的博客 1299

史上最全ThreadLocal 详解

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。

qq_43842093的博客 1万+

ThreadLocal-详解

ThreadLocal是Java提供的线程局部变量机制,通过为每个线程创建变量副本来实现线程隔离,避免同步开销。核心原理是利用Thread内部的ThreadLocalMap存储数据,以ThreadLocal实例为key。主要应用场景包括线程上下文传递、数据库连接管理、参数传递简化和线程安全工具类实现。但需警惕内存泄漏风险:ThreadLocalMap的key是弱引用而value是强引用,若不及时调用remove()方法可能导致内存泄漏。最佳实践包括:使用后立即remove()、声明为private stat

M_Reus_11的博客 1155

【Java】ThreadLocal详解

ThreadLocal 详解

啊松同学的博客 1443
上一篇: 代理设计模式
下一篇: Ubuntu安装配置Nginx
shmilychan
博客等级 码龄10年 71粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值