学习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. Android EditText____TextchangedListener

    今天在做APP的时候有个需求: EditText 动态监听内容变化如果长度为6时(就是看是不是验证码) 判断是否正确 正确就跳到下一个Activity,但是在Activity.finish()的时候, ...

  2. tensorflow兼容处理 tensorflow.compat.v1

    https://www.wandouip.com/t5i183316/ 引言 原来引用过一个段子,这里还要再引用一次.是关于苹果的.大意是,苹果发布了新的开发语言Swift,有非常多优秀的特征,于是很 ...

  3. python 类与类之间的关系. 特殊成员

    一.类与类之间的关系 1.依赖关系 在方法的参数位置把另一个类的对象作为参数进行传递 class Person: def play(self, tools): # 通过参数的传递把另一个类的对象传递进 ...

  4. BKDRhash

     哈希: 字符串(数字同理): 例如有100000个字符串,现在要插入一些字符串,插入前比较是否已经存在避免含有重复数据 用暴力计较的话会比较慢,在某字符串插入时,最好的情况是在第一个位置就遇见该字符 ...

  5. C#中的?操作符

    一.1个?的用法 1. 表示可空数据类型,如 int? bool? 2. 跟在对象后,如该对象为null,则不会触发空值异常,且整个表达式返回null,如: string kk = "123 ...

  6. Python--day71--内容回顾

  7. git 删除时报 the branch is not fully merged 这是什么意思

    今天删除本地分支 git branch -d XX 提示: the branch XXX is not fully merged原因:XXX分支有没有合并到当前分支的内容 解决方法:使用大写的D 强制 ...

  8. 在 CentOS 7.3 上安装 nginx 服务为例,说明在 Linux 实例中如何检查 TCP 80 端口是否正常工作

    CentOS 7.3 这部分以在 CentOS 7.3 上安装 nginx 服务为例,说明在 Linux 实例中如何检查 TCP 80 端口是否正常工作. 登录 ECS 管理控制台,确认实例所在安全组 ...

  9. 教你如何成为Spark大数据高手?

    教你如何成为Spark大数据高手? Spark目前被越来越多的企业使用,和Hadoop一样,Spark也是以作业的形式向集群提交任务,那么如何成为Spark大数据高手?下面就来个深度教程. Spark ...

  10. LuaForWindows_v5.1.4-45和lua-5.1.4.tar.gz

    Lua学习笔记(一) 安装调试环境 Lua学习笔记(一) 安装调试环境     觉得自己是该掌握一门脚本语言的时候了,虽然曾经用过C# 和JavaScript 写过Unity3D的脚本.但是,总觉得那 ...