ThreadLocal实现原理与应用

1.ThreadLocal的概念

        ThreadLocal即线程本地变量,或者称为线程变量。它是一个以ThreadLocal对象为key,任意对象为value的存储结构。这个存储结构附属在某个线程上。也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的值。也就是说ThreadLocal为变量在每个线程中都创建了一个副本,这样每个线程可以访问属于自己的变量副本,对这个变量副本的操作只会影响到该线程自身的数据,不会对其他线程中的数据造成影响。

2.ThreadLocal的实现原理

首先来看一下ThreadLocal的Set方法的源代码,如下:

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方法的作用是用来设置附属在线程上的本地数据,首先获取到当前线程,然后获取当前线程的ThreadLocalMap,如果这个map不存在就创建一个,然后设置本地变量的值。这里面的getMap(t)方法返回的是Thread类的一个成员属性threadLocals。

/**
* Get the map associated with a ThreadLocal. Overriden in 
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }

 在Thread类中,threadLocals的定义如下:

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

        在Thread类中并没有提供成员属性threadLocals的设置和访问的代码,对于threadLocals的操作需要通过ThreadLocal来实现,换句话说:ThreadLocal是线程Thread中成员属性threadLocals的管理者。对于ThreadLocal的get,set,remove操作对应的都是对线程实例中threadLocals变量的响应操作。

        然后在看一下ThreadLocalMap的定义,源码如下:

/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least on 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);
}

跟普通的Map定义有点不同,ThreadLocalMap内部有个Entry数组,将数据包装成静态内部类Entry对象,存储在table数组中,数组的下标是threadLocal的threadLocalHashCode&(INITIAL_CAPACITY-1),因为数组的大小是2的n次方,那其实这个值就是threadLocalHashCode%table.length,用&而不用%,其实是提升效率。只要数组的大小不变,这个索引下标就不会变。

    这里的Entry定义如下:

static class Entry extends WeakReference<ThreadLocal> {
    Object value;
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

Entry被继承WeakReference(弱引用),即当与根对象没有可达路径时就会被自动回收掉,从上面Entry的构造函数可以看出来,被WeakReference引用的对象是ThreadLocal本身,也就是说ThreadLocal对象当外界没有对它的引用后就会被GC回收。但是这里需要特别注意的是,Entry并不会被回收,它还在被ThreadLocalMap的table数组强引用着。所以为了避免造成内存泄漏,在使用ThreadLocal时有一个原则,即ThreadLocal使用后务必调用remove方法。

3.ThreadLocal的应用

        ThreadLocal在实际中有大量的应用,举个例子,Spring的声明式事务的实现机制,就是基于ThreadLocal来实现的。在实际应用中,ThreadLocal大致在如下场景中使用。

1).在进行对象跨层传递的时,使用ThreadLocal可以避免多次传递,打破层次间的约束;

2).线程间数据隔离;

3).进行事务操作,用于存储线程事务信息;

4).数据库连接,Session回话管理

ThreadLocal实例

        下面我们用一个简单的实例来说明ThreadLocal的使用,比如我们编写了一个数据库连接管理类:

class ConnectManager {
    private static Connection connect = null;
    public static Connection openConnection() {
        if (connect == null) {
            connect = DriverManager.getConnection();
        }
        return connect;
    }
    Public static void closeConnection() {
        if (connect != null)
            connect.close();
    }
}

我们使用数据库的时候首先就是建立连接,然后用完之后关闭就可以,这段代码有一个严重的问题,如果有1个客户端频繁使用数据库,那么就需要建立好多次连接和关闭,服务器可能会吃不消。这时候最好用ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之互不影响。

/**
 * 负责数据库连接定义的程序类
 * 该类可以负责所有操作线程的数据库连接,利用get()方法可以获得连接对象
 */ 
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 = "****" ;
    private static final String PASSWORD = "****" ;
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>() ;
    /**
     * 负责对外部提供获取的数据库连接对象,该对象通过ThreadLocal获取,如果当前线程没有保存的连接对象,则创建新的连接
     * @return 连接对象
     */
    public static Connection getConnection() {
        Connection conn = threadLocal.get() ;    // 先判断一下在ThreadLocal里是否有连接对象
        if(conn == null) {    // 第一次使用,没有连接,没有连接应该创建一个连接
            conn = connectionDatabase() ;    // 获取连接对象
            threadLocal.set(conn);     // 将刚刚创建好的连接对象保存在ThreadLocal之中                  
        }
        return conn ;    // 返回连接对象   
    }

    /**
     * 进行数据库的关闭处理。
     */
    public static void close() {
        Connection conn = threadLocal.get() ;
        if (conn != null) {    // 现在有连接对象了
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } 
            threadLocal.remove();     // 从当前线程之中删除掉指定连接       
        }
    }

    /**
     * 负责创建一个数据库连接对象
     * @return 数据库连接的实例化对象
     */
    private static Connection connectionDatabase() {    // 该方法只能本类调用
        Connection conn = null ;
        try {    // 一旦连接出现了错误,整个程序都无发执行            
            Class.forName(DBDRIVER) ;
            conn = DriverManager.getConnection(DBURL, USER, PASSWORD) ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn ;    // 获得数据库连接对象    
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ThreadLocal是Java中的一个线程本地变量工具类,用于在多线程环境下,为每个线程提供独立的变量副本。它的实现原理是通过在每个线程中维护一个ThreadLocalMap对象,该对象以ThreadLocal实例作为key,以对应的值作为value,存储线程的局部变量。 ThreadLocal应用场景有: 1. 线程安全的对象:如果一个对象在多线程环境中被共享访问,并且它的状态是可变的,那么使用ThreadLocal可以为每个线程提供一个独立的对象副本,避免线程间的竞争和同步问题。 2. 数据库连接管理:在使用数据库连接池时,每个线程需要从连接池中获取数据库连接进行操作,ThreadLocal可以用来存储当前线程所使用的数据库连接,保证每个线程都有自己独立的连接。 3. 事务管理:在多线程环境下,如果某个业务操作需要开启事务,并且事务的隔离级别是线程级别的,则可以使用ThreadLocal来存储当前线程的事务状态,确保每个线程操作的是自己的事务。 4. Web应用中的用户信息存储:在Web应用中,每个请求都会创建一个线程进行处理,可以使用ThreadLocal将用户信息保存在当前线程中,方便在不同的业务逻辑中获取用户信息。 5. 并发工具类中的上下文传递:在一些并发框架和工具类中,可能会需要将一些上下文信息传递给线程池中的线程,使用ThreadLocal可以将上下文信息绑定到当前线程,方便在线程池中进行访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值