ThreadLocal
提供了与线程绑定的变量,访问变量的每个线程都具有一个该变量的副本,它们都可以对这个变量的副本进行一些独立的操作(变量的这些副本是线程隔离的,所以不存在线程安全的问题),ThreadLocal
的实例经常是作为一个私有静态字段出现。在数据库连接复用的场景下(提供一个获取和关闭连接的方法),多个线程在对数据库操作时可以能会出现线程安全的问题,比如:
- 多线程获取数据库连接时,可能会创建很多连接;
- 一个线程在持有连接对数据库进行操作时,另一个线程却在调用连接的关闭方法;
如果使用同步锁去解决上面的问题,确实可以解决暴露的问题,但是在多线程场景下,同步锁的涉入必定会导致性能的急速下降,所以实际应用中很少会使用这种方式;还有就是各个线程需要使用数据库连接了,就直接就地new一个Connection出来,用完再关闭,这样也确实是一种方法,但是类似于数据库连接的创建和销毁都是非常耗费系统资源的,所以并不能满足连接复用的目的;这时使用ThreadLocal
在每个线程中对该数据库连接对象会创建一个副本,即每个线程内部都会有一个Connection,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能,但是由于它再每个线程中都创建了某个变量的副本,所以也需要考量它对系统内存资源的耗费。
ThreadLocal
提供了一些方法,get()
可以获取当前线程中保存的副本,其相关源码如下(做了部分作结):
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取ThreadLocalMap的Key和Value,参数是this,不是t
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// 获取成功返回ThreadLocalMap的Value
return result;
}
}
// 如果为获取到ThreadLocalMap,则调用额外方法
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
// 初始化一个null给value
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 如果为空塞个null进去
if (map != null)
map.set(this, value);
else
// 再创建一个新的ThreadLocalMap
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
getMap(Thread t)
方法最终是返回了一个ThreadLocalMap
对象:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
发现ThreadLocalMap
对象的Key是ThreadLocal
对象,每个Thread
中都有一个ThreadLocal.ThreadLocalMap threadLocals
,这个threadLocals
就是用来存储实际变量副本的,key为当前的ThreadLocal
,value就是变量的副本。在使用get()
时,如果没有变量的副本,那就初始化一个为null
的ThreadLocals
(此时直接调用会发生空指针异常),否则在主动调用set()
时初始化。ThreadLocal
在哪那么它就只对当前所在线程中的变量副本生效,不会对这个线程之外的线程产生作用。下面是网上的一个不错案例:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
// 先将当前线程的ID和Name设到对应的threadlocals中
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
// 将新创建的线程的属性设进去,再次查看
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
}
};
thread1.start();
// 必须等thread1执行完
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
输出为:
1
main
12
Thread-0
1
main
第一次输出是在Main线程中,设置了两个ThreadLocal,输出就只是在Main线程中设进去的变量,第二次是在thread1线程中,重新设置了两个ThreadLocal
,输出是变成了新设的值,等最后thread1线程执行完后,再次输出,因为此时是在Main线程中,所以输出的是在Main线程最开始设置的那两个值,Main线程和thread1线程之间是不影响的,thread1中的设置不会更改了在Main线程中设置的值。Main线程和thread1线程都有各自的threadlocals
变量。