学习Java中常用的开源框架,Mybatis、Hibernate中线程通过数据库连接对象Connection,对其数据进行操作,都会使用ThreadLocal类来保证Java多线程程序访问和数据库数据的一致性问题。就想深入了解一下ThreadLocal类是怎样确保线程安全的!详解如下:

一、对ThreadLocal类的大致了解

ThreadLocal ,也叫线程本地变量,可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了所使用的的变量副本。使用起来都是在线程的本地工作内存中操作,并且提供了set和get方法来访问拷贝过来的变量副本。底层也是封装了ThreadLocalMap集合类来绑定当前线程和变量副本的关系,各个线程独立并且访问安全!

public class DBUtil {
//创建一个存储数据库连接对象的ThreadLocal线程本地变量
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); static{
try {
//注册驱动
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
} catch (SQLException e) {
e.printStackTrace();
}
} /*
* 获取数据库的连接对象
*/
public static Connection getConnected(){
Connection conn = null;
conn = tl.get(); //第一步:从ThreadLocal对象当中去获取
if(conn == null){ //若没有获取到,原始方法获取
try {
conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.122.1:1521/xe","store","store_password");
//获取连接对象以后,都设置为默认手动提交
conn.setAutoCommit(false);
//第二部:将连接对象放入对应的ThreadLocal泛型对象tl当中(进而绑定到使用它的线程对象上)
tl.set(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
} /*
* 关闭数据库的连接,并删除对应的ThreadLocal中的对象
*/
public static void closeConnection(){
Connection conn = null;
conn = tl.get(); //第三步:使用完毕,再次获取对象
if(conn != null){
tl.remove(); //第四步:线程操作数据库完毕,移除
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

上述例子中使用ThreadLocal类来绑定对应线程和Connection之间的关系,确保访问数据库数据的安全性问题;大家想象一下,如果没有使用ThreadLocal类来绑定,那么多个线程同时进入getConnected()方法,有可能获取的是同一个Connection对象,导致线程不安全问题!

二、深入理解ThreadLocal类

(1)set操作,为线程绑定变量:

 public void set(T value) {
   Thread t = Thread.currentThread();//1.首先获取当前线程对象
ThreadLocalMap map = getMap(t);//2.获取该线程对象的ThreadLocalMap
if (map != null)
map.set(this, value);//如果map不为空,执行set操作,以当前threadLocal对象为key,实际存储对象为value进行set操作
else
createMap(t, value);//如果map为空,则为该线程创建ThreadLocalMap
}

可以很清楚的看到,ThreadLocal只不过是个入口,真正的变量副本绑定到当前线程上的。

    //Thread中的成员变量
    ThreadLocal.ThreadLocalMap threadLocals = null; //每个Thread线程中都封装了一个ThreadLocalMap对象     //ThreadLocal类中获取Thread类中的ThreadLocalMap对象
    ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
    }     //ThreadLocal类中创建Thread类中的ThreadLocalMap成员对象
    void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

现在,我们可以看出ThreadLocal的设计思想了:

(1) ThreadLocal仅仅是个变量访问的入口;

(2) 每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;

(3) ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法时通过threadLocal实例就可以找到绑定在当前线程上的副本对象。

看上去有点绕。我们完全可以设计成Map<Thread,Value>这种形式,一个线程对应一个存储对象。

ThreadLocal这样设计有两个目的:

第一:可以保证当前线程结束时,相关对象可以立即被回收;第二:ThreadLocalMap元素会大大减少,因为Map过大容易造成哈希冲突而导致性能降低。

(2)看get()方法

 public T get() {
   Thread t = Thread.currentThread();//1.首先获取当前线程
ThreadLocalMap map = getMap(t);//2.获取线程的map对象
if (map != null) {//3.如果map不为空,以threadlocal实例为key获取到对应Entry,然后从Entry中取出对象即可。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
}
void createMap(Thread t, T firstValue) {    //this指的是ThreadLocal对象
    t.threadLocals = new ThreadLocalMap(this, firstValue);
} 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;
}

三、应用场景:

ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如:由于JDBC的连接对象不是线程安全的,因此,当多个线程应用程序在没有协同的情况下,使用全局变量时,就是线程不安全的。通过将JDBC的连接对象保存到ThreadLocal中,每个线程都会拥有自己的连接对象副本。

ThreadLocal在Spring的事物管理,包括Hibernate管理等都有出现,在web开发中,有事会用来管理用户会话HttpSession,web交互这种典型的一请求一线程的场景似乎比较适合使用ThreadLocal,但是需要注意的是,由于此时session与线程关联,而Tomcat这些web服务器多采用线程池机制,也就是说线程是可以复用的,所以在每次进入的时候都需要重新进行set操作,或者使用完毕以后及时remove掉!

 public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());        //先获取ThreadLocalMap对象实例
if (m != null)        //直接通过threadLocal实例删除value值
m.remove(this);
}

ThreadLocal为什么会内存泄漏?

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

ThreadLocal如何防止内存泄漏?

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就需要清理。

深入ThreadLocal的底层实现机制以及对应的使用风险的更多相关文章

  1. <转>ASP.NET学习笔记之理解MVC底层运行机制

    ASP.NET MVC架构与实战系列之一:理解MVC底层运行机制 今天,我将开启一个崭新的话题:ASP.NET MVC框架的探讨.首先,我们回顾一下ASP.NET Web Form技术与ASP.NET ...

  2. ThreadLocal和线程同步机制对比

    共同点: ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题. 区别: 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量. 这时该变量是多个线程共享的,使用同 ...

  3. 【Spark 深入学习 04】再说Spark底层运行机制

    本节内容 · spark底层执行机制 · 细说RDD构建过程 · Job Stage的划分算法 · Task最佳计算位置算法 一.spark底层执行机制 对于Spark底层的运行原理,找到了一副很好的 ...

  4. .net core系列之《从源码对Configuration的底层运行机制进行分析》

    通过对Configuration源代码的分析从而来自定义一个配置数据源 1.用反编译工具来看看AddJsonFile()这个方法究竟干了什么,源代码如下: public static IConfigu ...

  5. ReentrantLock的底层实现机制 AQS

    ReentrantLock的底层实现机制是AQS(Abstract Queued Synchronizer 抽象队列同步器).AQS没有锁之类的概念,它有个state变量,是个int类型,为了好理解, ...

  6. Spring AOP简介与底层实现机制——动态代理

    AOP简介 AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想.AOP 是 OOP(面向对象编程 Object Oriented Programmi ...

  7. Spring的底层实现机制

    Spring的底层实现机制是通过Demo4j+java反射机制实现的. 使用demo4j来解析xml,使用反射机制实例化bean.

  8. X windows的底层实现机制

    Qt在Linux上运行崩溃了,很可能的原因是对于X11机制的不了解.很可能是UI代码里面对窗口的操作不规范而导致Qt内部的BUG暴露出来.具体UI实现代码我也没有看.是别人维护的.打算今天去看下代码, ...

  9. java面试题之什么是ThreadLocal?底层如何实现的?

    ThreadLocal是一个解决线程并发问题的一个类,用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术.但是当我们不想使用同步的时 ...

随机推荐

  1. Linux保证运行一个实例

    1. ; // 默认最大路径长度 inline std::string current_exe_name() { }; int ret = readlink("/proc/self/exe& ...

  2. Java练习 SDUT-3106_小鑫数数儿

    小鑫数数儿 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 某天小鑫忽然得到了许多的数字,他很好学,老师给他布置了一个任 ...

  3. kubernetes API 访问控制在阿里云容器服务(ACK)上的实践

    提起K8s API的访问控制,很多同学应该都会想到RBAC,这是K8s用来做权限控制的方法,但是K8s对API的访问控制却不止于此,今天我们就来简单介绍下K8s的访问控制以及ACK如何利用这套方法提供 ...

  4. mysql数据库之windows版本

    安装  第一步:打开网址,http://www.mysql.com.点击downloads之后跳转到http://www.mysql.com/downloads/选择Community选项 第二步:按 ...

  5. Project Euler Problem 15-Lattice paths

    组合数,2n中选n个.向右走有n步,向下走有n步.共2n步.有n步是向右走的,计算向右走的这n步的所有情况,即C(2n,n). 或者,每一步,只能从右边或者上边走过来,只有这两种情况,即step[i] ...

  6. 梯度优化算法Adam

    最近读一个代码发现用了一个梯度更新方法, 刚开始还以为是什么奇奇怪怪的梯度下降法, 最后分析一下是用一阶梯度及其二次幂做的梯度更新.网上搜了一下, 果然就是称为Adam的梯度更新算法, 全称是:自适应 ...

  7. H3C IP的主要作用

  8. JavaScript跨域问题

    通过实现Ajax通信的主要限制,来源于跨域安全策略.默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源.这种安全策略可以预防某些恶意行为.但是,实现合理的跨域请求对于开发某些浏览器应用程 ...

  9. 【Learning Notes】线性链条件随机场(CRF)原理及实现

    1. 概述条件随机场(Conditional Random Field, CRF)是概率图模型(Probabilistic Graphical Model)与区分性分类( Discriminative ...

  10. IntStack(存放int型值,带迭代器) 附模板化实现 p406

    1 该栈只用于存在int型数据 #include "../require.h" #include <iostream> using namespace std; cla ...