线程安全问题的由来
传统的Web开发是通过实现Servlet对象来进行Http请求的响应。当Web容器收到一个HTTP请求时,Web容器中的一个主调度线程会从事先定义好的线程池中分配一个当前的工作线程,将请求分配给当前的工作线程,由线程来执行对应的Servlet对象的Service方法。因此,对于同一个Servlet对象的多个请求,Servlet的Service方法将在一个多线程的环境中并发执行。即Web容器默认采用单实例(单Servlet实例)多线程的方式来处理Http请求。这样会导致变量访问的线程安全问题。
ThreadLocal实现线程安全的原理
ThreadLocal类在维护变量时,使用了当前线程的一个叫做ThreadLocalMap的独立副本,每个线程可以独立修改属于自己的副本而不会互相影响,从而达到隔离线程的目的,避免线程访问实例变量发生冲突的问题。
Threadlocal本身不是一个线程,是通过操作当前线程中的一个内部变量来达到与其他线程隔离的目的。Thread的类的部分源码如下:
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
可以看到ThreadLocalMap是Thread的本地实例变量。ThreadLocal的部分源码如下:
public class ThreadLocal<T> {
//将value值保存到当前线程的本地变量中
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static class ThreadLocalMap {
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
从源码中很容易看出,每个线程对象只能访问自己的ThreadLocalMap,实现了线程间数据访问的隔离。此外,ThreadLocalMap中是以ThreadLocal实例作为key的,因此在同一个线程中,不同的ThreadLocal实例操作对象之间也是隔离的。
注意:ThreadLocal模式核心在于实现一个数据共享的环境(在类内部封装一个ThreadLocal的静态实例),解决同一线程中隶属不同开发层次的数据共享问题,并不是实现数据传递。
ThreadLocal模式的核心元素
要完成Threadlocal模式,最关键的地方就是创建一个任何地方都可以访问的ThreadLocal实例。这一点可以通过静态实例变量来实现,这个用于承载静态实例变量的类就被是做一个共享环境。举例如下:
public class ActionContext implements Serializable {
static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();
}
这是Struts2中的ActionContext类的部分源码,采用ThreadLocal实现ActionContext的线程安全。
总结:
引入ThreadLocal模式的两个步骤:
1.建立一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享的数据环境。
2.在类中实现访问静态ThreadLocal变量的静态方法(设值和取值)。
Struts2使用ThreadLocal模式的好处:
1.使数据在不同编程层次得到有效共享。
2.对执行逻辑与执行数据进行有效解耦。一般情况下,Java对象之间的协作关系主要通过参数和返回值进行消息传递,这是对象协作之间的一个重要依赖。ThreadLocal模式打破了这种依赖关系,通过线程安全的共享对象来进行数据共享,有效的避免了在编程层次之间形成数据依赖。