ThreadLocal源码笔记
博客专区 > JoeyXie 的博客 > 博客详情
ThreadLocal源码笔记
JoeyXie 发表于2年前
ThreadLocal源码笔记
  • 发表于 2年前
  • 阅读 19
  • 收藏 0
  • 点赞 1
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

        最近用到了ThreadLocal,所以看了一下JDK的源码。ThreadLocal是以空间换时间的典型,因为它避免了使用synchronized关键字来对变量加锁,从而节约了很多时间。ThreadLocal是为每个线程创建一个变量的副本,所以每个线程都可以访问这个副本的内容并修改,彼此之间的副本不会相互影响,因此看似会修改同一个变量,但实际上线程间修改的都是线程本地变量。例如下面这个数据库连接类:

public class DatabaseUtil {
    private static final ThreadLocal<Connection> CON_HOLDER = new ThreadLocal<Connection>();

    public static Connection getConn() {
        Connection con = CON_HOLDER.get();
        if(null == con) {
            try {
                con = DriverManager.getConnection(url, username, pwd);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                CON_HOLDER.set(con);
            }
        }
        return con;
    }

    // 其它函数
    ......
}

       我们可以在Servlet中调用getConn()方法去获取一个连接,然后执行相应的数据库操作(暂且忽略这种做法的不规范性,先讨论多线程访问的问题)。Servlet在Java web中是单实例多线程的执行模式,这也是为什么我们一般不在Servlet类中定义成员变量的原因,就是为了避免线程不安全。那么当多个请求到来时,每个请求都是在一个线程里面来访问servlet的方法的,因此看上去每个线程获取到的连接都是同一个实例(getConn方法类似单例模式)。但实际上并不是这样,每个请求都会在它的线程中获取到不同的Connection连接,这就是ThreadLocal的作用。首先看ThreadLocal的get()方法的源码:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

      首先它获取了当前正在运行的线程,getMap()方法其实就是获取Thread中的ThreadLocalMap对象,并没有什么特别的。然后从ThreadLocalMap中根据当前ThreadLocal对象作为key去获取对应的值,接着返回。Thread类中有以下两个成员值得注意:

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

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

     刚才的getMap()方法其实返回的就是threadLocal对象,现在Thread、ThreadLocal和ThreadLocalMap对象混在一起有点乱,下面来理清楚它们的关系:

     1. 每个Thread都有一个ThreadLocalMap对象threadLocals作为它的成员变量,它的key是ThreadLocal对象,值就是ThreadLocal中保存的值,如果一个线程访问了多个ThreadLocal对象,则ThreadLocalMap中就会有多个映射关系

     2. 每一个ThreadLocal对象都有自己唯一的hash值,这个值就是用于在ThreadLocalMap中查找对应的value的,所以以ThreadLocal对象为key就可以找到对应的线程本地的值

     3. ThreadLocalMap是ThreadLocal的一个静态内部类(就把它想象成一个普通的类就好了,只不过访问它需要通过ThreadLocal来访问),保存了很多个entry,每个entry都是一个ThreadLocal与它的值的映射关系

      说了这么多,还是贴一张图比较直观:

    从上图可以看到,每个线程都拥有一个ThreadLocalMap对象,它会保存ThreadLocal和value的映射关系,ThreadLocal就相当于一个中间人,每次线程方法它的get或set方法时,它都会首先获取当前运行的线程,然后获取线程中的ThreadLocalMap对象,由于每个ThreadLocal对象都有唯一hash值,所以以ThreadLocal为key可以在当前线程中找到它对应的值。每个线程都维护自己的ThreadLocalMap对象,这个就是真正的“副本”,通过ThreadLocal这个中介就可以保证成员变量的线程安全。每个线程都有自己的映射(即ThreadLocalMap),当线程第一次设置ThreadLocal的值的时候,其实就是在自己的map中添加一条访问的ThreadLocal对象和值的映射,创建副本,然后就可以根据该ThreadLocal对象(也就是它的hashcode)找到对应的值,而不需要访问共享的变量。

    ThreadLocal没有使用synchronized关键字,而是在Thread中维护一份本地的副本,如果每个线程都希望独享自己的成员变量时,ThreadLocal是一个好选择,但如果确实需要多线程共享一个成员变量时,那么还是需要使用synchronized关键字来加锁保证线程安全的。

标签: ThreadLocal
共有 人打赏支持
粉丝 0
博文 21
码字总数 26459
×
JoeyXie
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: