ThrealLocal用于提供线程内部的局部变量,
这种变量在多线程环境下访问能保证各个线程里的变量相对独立于其他线程内的变量,
也就是数据隔离。
简单例子
先看一个简单示的ThreadLocal示例,创建五个线程,每个线程都会,
1. 获取当前日期秒数
2. 存入一个变量中
3. 取出变量打印
final ThreadLocal<Long> secs = new ThreadLocal<Long>();
for(int i=0; i<5; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
public void run() {
secs.set(System.currentTimeMillis());
System.out.println(secs.get());
}
}).start();
}
如果对于变量不采取ThreadLocal控制,那么每个线程对变量的读写过程将变得线程不安全了。
很多情况,都是对于DB数据库的连接访问会采取ThreadLocal处理。
如下代码片段,
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
ThreadLocal的好处
- 数据隔离
- 简化程序,易读简洁
ThreadLocal的用法
- void set(Object value)
设置当前线程的线程局部变量的值。 - public Object get()
该方法返回当前线程所对应的线程局部变量。 - public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 - protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
ThreadLocal的原理
早期实现
听说在JDK早期的代码中,大概是这么实现的,
1.在ThreadLocal中维护一个Map
2.当前的Thread作为key,而要存储的实例变量作为value
如今实现
由于上述的那种方法会导致效率太低,所以出现了现在的实现方法
1.在Thread中维护一个Map
2.当前的ThreadLocal作为key,而要存储的实例变量作为value
简单类图
源码解析
Thread.java类中的Map,用于保存ThreadLocal和实例变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
tips:ThreadLocalMap是扩展于WeakReference的类,定义在ThreadLocal内部中。
那么定义在线程中的ThreadLocalMap是如何初始化的呢?
初始化的过程在ThreadLocal.java中,会在调用set(Object value)方法的时候触发:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
tips:这个方法会判断当前线程的Map是否初始化,
如果有,赋值。
如果没有,初始化,再赋值。
初始化的代码如下,
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
tips:将当前线程中的Map初始化,再赋值