在JDK1.2 版本中,提供了java.lang.ThreadLocal。它为解决多线程并发问题提供了一种新的思路,有其特定的应用场景。
1. 如何理解ThreadLocal
ThreadLocal,很多人叫它线程本地变量。它为每个线程都创建一个副本,每个线程访问自己内部的副本变量。不会影响其他线程的副本变量。变量是同一个,但是每个线程都使用此变量的一个新的副本,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal一般可用于数据库连接的管理,可看下边的例子:
public class ConnectionManage private static Connection connection = null;
/**
* 获取数据量连接
*/
public static Connection openConnection() {
if (connection == null) {
connection = DriverManager.getConnection();
}
return connection;
}
/**
* 关闭数据库连接
*/
public static void closeConnection(){
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
上边的代码在多线程中使用会出现问题。因为connection是共享变量,这两个方法没有进行同步,可能在openConnection方法中,多次创建connection。另外,可能一个线程在执行数据库操作时,另个一个线程就调用了closeConnection方法,将数据库连接关闭了。
所以,如果要保证上边的代码正确运行,必须进行同步。但是,这会大大影响执行效率,因为,一个线程在使用Connection时,其他线程就必须等待。
我们可以这样思考,是不是可以不将connection变量设为线程私有变量,不进行共享。这样是可以的,如果直接声明成:
private Connection connection = null;
然后,给每个线程创建私有的变量。这样是可以实现线程安全的,但是,这创建了很多个数据库连接,这会使服务器压力很大,严重影响程序的执行效率。并且,数据库连接一般是设计为单例的。所以,不能设计成这种形式。
这种情况下,就可以使用ThreadLocal。ThreadLocal在每个线程都为共享变量创建了副本,即:会在每个线程的内部都会有一个该变量,且不同线程之间不会影响,从而达到了线程隔离的效果。
1.ThreadLocal的实现
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份共享变量的副本,因此可以同时访问而互不影响。
private static ThreadLocal<Connection> connection = new ThreadLocal<Connection>();
ThreadLocal有4个方法,分别是:
- public void set(T value):将值放入线程局部变量中
- public T get():从线程局部变量中获取值
- public void remove():从线程局部变量中移除值(有助于 JVM 垃圾回收)(jdk1.5)
protected T initialValue():返回线程局部变量中的初始值(默认为 null)
get方法的实现:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
从上边的代码可以看到,getMap(t)方法,获得的当前线程的一个ThreadLocalMap。然后获得Entry <key,value>。获取成功,则返回本线程的value。如果,map为空,说明还没有此变量副本。调用setInitialValue()方法。
下边,进行分析每一行的具体含义:
ThreadLocalMap map = getMap(t);
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从上边的代码可以看到,返回的是当前线程的一个成员变量threadLocals。这个成员变量在Thread类中:
定义为:
ThreadLocal.ThreadLocalMap threadLocals = null;
这个变量的类型是ThreadLocal类的内部类ThreadLocalMap。然后进入ThreadLocal中,查看此类型实现:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocalMap的Entry继承了 WeakReference,并且使用当前ThreadLocal作为键值。这里将ThreadLocal作为键值,是因为可能一个线程中有多个ThreadLocal的变量。而每个线程是 ThreadLocal.ThreadLocalMap threadLocals = null;获得线程独有的Entry的。
然后,再看setInitialValue()方法。
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
如果获得的Map不为空,就设置键值对,为空,就调用createMap方法:
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
设置当前线程的成员变量threadLocals。
这基本就是ThreadLocal的实现原理,总结一下就是:在每个线程的Thread内部有一个 ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它存储了实际的变量副本。键值为当前ThreadLocal变量,value为变量副本。
初始时候,在Thread中,threadLocals为空,当ThreadLocal变量调用get()或者set()方法时,会对Thread类中的threadLocals进行初始化,键值为当前ThreadLocal变量,value为变量副本。
变量副本是存在线程的Thread类中的threadLocals变量中的。
## 注意点##
- 在进行get之前,必须进行set,否则会出现空指针。 如果想在set之前就调用get,则必须重写initialValue方法。
原因是: 如果没有set的话,则map返回为空,则会调用setInitialValue方法,此方法创建副本,并且返回value。value的获得是通过:T value = initialValue();获得的,如果没有重写这个方法,其默认实现是:
protected T initialValue() {
return null;
}
其返回值是null,从而,没有先set,就调用get时,会返回null,从而报空指针异常。
set方法为:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- ThreadLocal常见的应用场景
一般用ThreadLocal来解决数据库连接和Session管理等。ThreadLocal主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。