ThreadLocal源码笔记
2016-02-02 00:00
381 查看
最近用到了ThreadLocal,所以看了一下JDK的源码。ThreadLocal是以空间换时间的典型,因为它避免了使用synchronized关键字来对变量加锁,从而节约了很多时间。ThreadLocal是为每个线程创建一个变量的副本,所以每个线程都可以访问这个副本的内容并修改,彼此之间的副本不会相互影响,因此看似会修改同一个变量,但实际上线程间修改的都是线程本地变量。例如下面这个数据库连接类:
我们可以在Servlet中调用getConn()方法去获取一个连接,然后执行相应的数据库操作(暂且忽略这种做法的不规范性,先讨论多线程访问的问题)。Servlet在Java web中是单实例多线程的执行模式,这也是为什么我们一般不在Servlet类中定义成员变量的原因,就是为了避免线程不安全。那么当多个请求到来时,每个请求都是在一个线程里面来访问servlet的方法的,因此看上去每个线程获取到的连接都是同一个实例(getConn方法类似单例模式)。但实际上并不是这样,每个请求都会在它的线程中获取到不同的Connection连接,这就是ThreadLocal的作用。首先看ThreadLocal的get()方法的源码:
首先它获取了当前正在运行的线程,getMap()方法其实就是获取Thread中的ThreadLocalMap对象,并没有什么特别的。然后从ThreadLocalMap中根据当前ThreadLocal对象作为key去获取对应的值,接着返回。Thread类中有以下两个成员值得注意:
刚才的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关键字来加锁保证线程安全的。
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关键字来加锁保证线程安全的。
相关文章推荐
- 简单分析Java线程编程中ThreadLocal类的使用
- 实例讲解Java并发编程之ThreadLocal类
- Java多线程编程之ThreadLocal线程范围内的共享变量
- Python 8.3 ThreadLocal
- Struts2中TreadLocal设计模式详解
- Java并发编程:深入剖析ThreadLocal
- 利于ThreadLocal管理Hibernate Session
- 项目问题总结一、全局变量引起的并发问题
- 正确理解ThreadLocal
- java的ThreadLocal简介和示例
- ThreadLocal
- ThreadLocal封装Connection--事务统一管理
- ThreadLocal实现线程范围的共享变量 代码示例
- ThreadLocal知识系列总结1
- ThreadLocal
- ThreadLocal保证线程安全的原理
- 彻底理解ThreadLocal
- ThreadLocal模式的实现机理
- ThreadLocal的奥秘
- 正确理解ThreadLocal