您的位置:首页 > 其它

深入解析ThreadLocal底层实现原理

2019-03-26 11:03 691 查看

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

一、对其ThreadLocal类的大致了解

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

    

[java] view plain copy
  1. public class DBUtil {  
  2.     //创建一个存储数据库连接对象的ThreadLocal线程本地变量  
  3.     private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();  
  4.   
  5.     static{  
  6.         try {  
  7.             //注册驱动  
  8.             DriverManager.registerDriver(new oracle.jdbc.OracleDriver());  
  9.         } catch (SQLException e) {  
  10.             e.printStackTrace();  
  11.         }  
  12.     }  
  13.       
  14.     /* 
  15.      * 获取数据库的连接对象 
  16.      */  
  17.     public static Connection getConnected(){  
  18.         Connection conn = null;  
  19.         conn = tl.get();        //第一步:从ThreadLocal对象当中去获取  
  20.         if(conn == null){       //若没有获取到,原始方法获取  
  21.             try {  
  22.                 conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.122.1:1521/xe","store","store_password");  
  23.                 //获取连接对象以后,都设置为默认手动提交  
  24.                 conn.setAutoCommit(false);    
  25.                 //第二部:将连接对象放入对应的ThreadLocal泛型对象tl当中(进而绑定到使用它的线程对象上)  
  26.                 tl.set(conn);  
  27.             } catch (SQLException e) {  
  28.                 e.printStackTrace();  
  29.             }  
  30.         }  
  31.         return conn;  
  32.     }  
  33.   
  34.     /* 
  35.      * 关闭数据库的连接,并删除对应的ThreadLocal中的对象 
  36.      */  
  37.     public static void closeConnection(){  
  38.         Connection conn = null;  
  39.         conn = tl.get();        //第三步:使用完毕,再次获取对象  
  40.         if(conn != null){  
  41.             tl.remove();        //第四步:线程操作数据库完毕,移除  
  42.             try {  
  43.                 conn.close();  
  44.             } catch (SQLException e) {  
  45.                 e.printStackTrace();  
  46.             }  
  47.         }  
  48.     }  
  49. }  

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

二、深入理解ThreadLoca类

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

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

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

[java] view plain copy
    [li]    //Thread中的成员变量  
  1.     ThreadLocal.ThreadLocalMap threadLocals = null;     //每个Thread线程中都封装了一个ThreadLocalMap对象  
  2.           [/li]
  3.     //ThreadLocal类中获取Thread类中的ThreadLocalMap对象  
  4.     ThreadLocalMap getMap(Thread t) {  
  5.     return t.threadLocals;  
  6.     }  
  7.           
  8.     //ThreadLocal类中创建Thread类中的ThreadLocalMap成员对象  
  9.     void createMap(Thread t, T firstValue) {  
  10.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  11.     }  

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

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

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

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

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

ThreadLocal这样设计有两个目的:

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

(2)看get()方法

[java] view plain copy
  1. public T get() {  
  2.     Thread t = Thread.currentThread();//1.首先获取当前线程  
  3.         ThreadLocalMap map = getMap(t);//2.获取线程的map对象  
  4.         if (map != null) {//3.如果map不为空,以threadlocal实例为key获取到对应Entry,然后从Entry中取出对象即可。  
  5.             ThreadLocalMap.Entry e = map.getEntry(this);  
  6.             if (e != null)  
  7.                 return (T)e.value;  
  8.         }  
  9.         return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值  
  10.     }  
[java] view plain copy
  1. void createMap(Thread t, T firstValue) {    //this指的是ThreadLocal对象  
  2.     t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3. }  
  4.           
  5. private T setInitialValue() {  
  6.     T value = initialValue();  
  7.     Thread t = Thread.currentThread();  
  8.     ThreadLocalMap map = getMap(t);  
  9.     if (map != null)  
  10.          map.set(this, value);  
  11.     else  
  12.         createMap(t, value);  
  13.         return value;  
  14. }  

三、应用场景:

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

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

    [java] view plain copy
  1. public void remove() {  
  2.         ThreadLocalMap m = getMap(Thread.currentThread());        //先获取ThreadLocalMap对象实例  
  3.         if (m != null)        //直接通过threadLocal实例删除value值  
  4.             m.remove(this);  
  5.     }  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: