ThreadLocal详解
ThreadLocal在Struts2、spring、hibernate中都有很重要的应用。
ThreadLocal用来解决多线程程序的并发问题
ThreadLocal并不是一个Thread,而是Thread的局部变量
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量
这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明
Java没有提供在语言级支持(语法上),而是变相地通过ThreadLocal的类提供支持。
ThreadLocal类中的方法
(1)将此线程局部变量的当前线程副本中的值设置为指定值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
(2)移除此线程局部变量当前线程的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
(3)返回此线程局部变量的当前线程副本中的值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocal的原理
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
自己模拟ThreadLocal
public class SimpleThreadLocal{
private Map valueMap=Collections.synchronizedMap(new HashMap());
public void set(Object newValue){
valueMap.put(Thread.currentThread(),newValue);//键为线程对象,值为本线程的变量副本
}
public Object get(){
Thread currentThread=Thread.currentThread();
Object o=valueMap.get(currentThread);//返回本线程对应的变量
if(o==null&&!valueMap.containsKey(currentThread)){
//如果在Map中不存在,放到Map中保存起来
o=initialValue();
valueMap.put(currentThread,o);
}
return o;
}
public void remove(){
valueMap.remove(Thread.currentThread());
}
public void initialValue(){
return null;
}
}
使用ThreadLocal的具体例子
public class SequenceNumber{
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seNum=new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0;
}
}
public Integer getNextNum(){
seNum.set(seNum.get()+1);
return seNum.get();
}
public static void main(String[] args){
SequenceNumber sn=new SequenceNumber();
//3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
this.sn=sn;
}
public void run(){
//每个线程打印3个序列号
for(int i=0;i<3;i++){
System.out.println("thread["+
Thread.currentThread().getName()+",sn["+sn.getNextNum()+"]");
}
}
}
}
threadlocal的应用
处理事务
Struts2中
当Struts2接收到浏览器发出的url请求时,先经过很多拦截器进行处理,然后执行action动作类中的动作方法,根据动作方法的返回值找到对应的结果视图,返回给页面。
而这一系列的拦截器以及处理请求业务逻辑的action动作类的动作方法都要使用ValueStack值栈对象,那么如何保证ValueStack值栈的并发问题?
查看Struts2源码,在ActionContext中,创建了一个ThreadLocal类型的属性,属性名称为actionContext:
这样一来,相当于在struts2的整个生命周期内(ActionContext类对象,在整个生命周期内有效)不管是拦截器、动作类都可以通过ActionContext.getContext()方法获取放入到ThreadLocal中的ActionContext的实例化对象。从而保证:
ValueStack valueStack = ActionContext.getContext().getValueStack() ;
获取到的是本地线程ThreadLocal的属性actionContext中的valueStack,确保值栈ValueStack的多线程操作的安全问题。