笔者在学习spring3中spring对事务管理的过程中,接触到了ThreadLocal的相关内容,比如在spring中,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但模板类并未采用线程同步机制,因为线程同步会降低并发性,影响系统性能。此外。通过代码同步解决线程安全的挑战性很大,可能会增加好几倍的实现难度。因此在spring中,通过ThreadLocal,可以在无须线程同步的情况下九华街线程安全的难题,同时,ThreadLocal在管理request作用域的Bean、事务管理、任务调度、AOP等模块都起着重要作用,在Spring中他有着举足轻重的作用,因此笔者希望借这个机会,将自己用到的ThreadLocal相关的内容和查阅资料获得相关内容整理下,分享阅读本文的同行。
那么,ThreadLocal是什么
早在JDK1.2版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的问题提供了一种新思路。使用这个工具类可以很简洁额写出优美的多线程程序。
ThreadLocal,他不是一个线程,而是线程的一个本地化对象。一般情况下,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。我们该注意的还有:如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
接下来,我们看一下ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法
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的使用
public class SequenceNumber {
public static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
public int getNextum(){
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
private static class TestClient extends Thread{
private SequenceNumber sn = new SequenceNumber();
public TestClient(SequenceNumber sn){
this.sn = sn;
}
public void run(){
for(int i = 0;i < 3;i++){
System.out.println("thread[" + Thread.currentThread().getName() +
"] sn[" + sn.getNextum() + "]");
}
}
}
public static void main(String[] args){
SequenceNumber sn = new SequenceNumber();
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
在这里SequenceNumber类包含了一个静态ThreadLocal类变量,这个类其实是ThreadLocal的子类,并且提供初始的变量值,TestClient线程产生一组序列号,我们生成三个TestClient,并且我们将同一个SequenceNumber类注入到这三个TestClient对象中,在控制台中输出一下结果:
我们发现虽然每个线程获得的是同一个SequenceNumber对象,但是他们并没有发生互相干扰的情况,而是各自产生独立的序列号。
我们可以看下在JDK1.5中ThreadLocal的部分源码:
/**
* Returns the value in the current thread's copy of this thread-local
* variable. Creates and initializes the copy if this is the first time
* the thread has called this 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)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Many applications will have no need for
* this functionality, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current threads' copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我们看到在set方法中,ThreadLocal利用一个ThreadLocalMap类型的map来保存当前线程中的某个变量值,而且这个key值就是这个线程中的threadLocals,这个threadLocals可以将线程自己的对象保持到其中,从而保证线程可以正确的访问到自己的对象。
总之ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1.每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2.将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
如果有问题,欢迎大家提出来!