线程本地变量ThreadLocal源码解读
一、ThreadLocal基础知识
原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但是Spring中的各种模板类并未采用线程同步机制,因为线程同步会影响并发性和系统性能,而且实现难度也不小。
ThreadLocal在Spring中发挥着重要的作用。在管理request作用域的bean,事务管理,任务调度,AOP等模块中都出现了它的身影。
ThreadLocal介绍: 它不是一个线程,而是线程的一个本地化对象,或者说是局部变量。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象
如何存放线程本地变量?
在ThreadLocal类中有一个ThreadLocalMap, 用于存放每一个线程的变量副本,Map中元素的键为线程对象,值为对应线程的变量副本。
二、源码解读
首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题,比如Hibernate中的OpenSessionInView,就是使用ThreadLocal保存Session对象,还有我们经常用ThreadLocal存放Connection,代码如:
1 1/** 2 * 数据库连接管理类 3 * @author 爽 4 * 5 */ 6 6public class ConnectionManager { 7 8 /** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */ 9 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); 10 11 public static Connection getCurrConnection() { 12 // 获取当前线程内共享的Connection 13 Connection conn = threadLocal.get(); 14 try { 15 // 判断连接是否可用 16 if(conn == null || conn.isClosed()) { 17 // 创建新的Connection赋值给conn(略) 18 // 保存Connection 19 threadLocal.set(conn); 20 } 21 } catch (SQLException e) { 22 // 异常处理 23 } 24 return conn; 25 } 26 27 /** 28 * 关闭当前数据库连接 29 */ 30 public static void close() { 31 // 获取当前线程内共享的Connection 32 Connection conn = threadLocal.get(); 33 try { 34 // 判断是否已经关闭 35 if(conn != null && !conn.isClosed()) { 36 // 关闭资源 37 conn.close(); 38 // 移除Connection 39 threadLocal.remove(); 40 conn = null; 41 } 42 } catch (SQLException e) { 43 // 异常处理 44 } 45 } 46 }
这样处理的好处:
1 统一管理Connection;
2 不需要显示传参Connection,代码更优雅;
3 降低耦合性。
ThreadLocal有四个方法,分别为:
initialValue
protected T initialValue()
返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
返回:
返回此线程局部变量的初始值
get
public T get()
返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本。
返回:
此线程局部变量的当前线程的值
set
public void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于initialValue() 方法来设置线程局部变量的值。
参数:
value - 存储在此线程局部变量的当前线程副本中的值。
remove
public void remove()
移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
很多人对ThreadLocal存在一定的误解,说ThreadLocal中有一个全局的Map,set时执行map.put(Thread.currentThread(), value),get和remove时也同理,但SUN的大师们是否是如此实现的,我们只能去看源码了。
set方法:
1 /** 2 * Sets the current thread's copy of this thread-local variable 3 * to the specified value. Most subclasses will have no need to 4 * override this method, relying solely on the {@link #initialValue} 5 * method to set the values of thread-locals. 6 * 7 * @param value the value to be stored in the current thread's copy of 8 * this thread-local. 9 */ 10 public void set(T value) { 11 // 获取当前线程对象 12 Thread t = Thread.currentThread(); 13 // 获取当前线程本地变量Map 14 ThreadLocalMap map = getMap(t); 15 // map不为空 16 if (map != null) 17 // 存值 18 map.set(this, value); 19 else 20 // 创建一个当前线程本地变量Map 21 createMap(t, value); 22 } 23 24 /** 25 * Get the map associated with a ThreadLocal. Overridden in 26 * InheritableThreadLocal. 27 * 28 * @param t the current thread 29 * @return the map 30 */ 31 ThreadLocalMap getMap(Thread t) { 32 // 获取当前线程的本地变量Map 33 return t.threadLocals; 34 }
这里注意,ThreadLocal中是有一个Map,但这个Map不是我们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样做的好处是,每个线程都对应一个本地变量的Map,所以一个线程可以存在多个线程本地变量。
get方法:
1 /** 2 * Returns the value in the current thread's copy of this 3 * thread-local variable. If the variable has no value for the 4 * current thread, it is first initialized to the value returned 5 * by an invocation of the {@link #initialValue} method. 6 * 7 * @return the current thread's value of this thread-local 8 */ 9 public T get() { 10 Thread t = Thread.currentThread(); 11 ThreadLocalMap map = getMap(t); 12 if (map != null) { 13 ThreadLocalMap.Entry e = map.getEntry(this); 14 if (e != null) 15 return (T)e.value; 16 } 17 // 如果值为空,则返回初始值 18 return setInitialValue(); 19 }
了之前set方法的分析,get方法也同理,需要说明的是,如果没有进行过set操作,那从ThreadLocalMap中拿到的值就是null,这时get方法会返回初始值,也就是调用initialValue()方法,ThreadLocal中这个方法默认返回null。当我们有需要第一次get时就能得到一个值时,可以继承ThreadLocal,并且覆盖initialValue()方法
优秀博文:
内容来自:cnblogs:牛奶、不加糖
学问:纸上得来终觉浅,绝知此事要躬行
为事:工欲善其事,必先利其器。
态度:道阻且长,行则将至;行而不辍,未来可期
.....................................................................
------- 桃之夭夭,灼灼其华。之子于归,宜其室家。 ---------------
------- 桃之夭夭,有蕡其实。之子于归,宜其家室。 ---------------
------- 桃之夭夭,其叶蓁蓁。之子于归,宜其家人。 ---------------
=====================================================================
* 博客文章部分截图及内容来自于学习的书本及相应培训课程以及网络其他博客,仅做学习讨论之用,不做商业用途。
* 如有侵权,马上联系我,我立马删除对应链接。 * @author Alan -liu * @Email no008@foxmail.com
转载请标注出处! ✧*꧁一品堂.技术学习笔记꧂*✧. ---> https://www.cnblogs.com/ios9/