类图如下:
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new对象的操作创建对象来实现的。每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。下面的代码为证:
定义一个User类:
package p;
public class User {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String toString() {
return firstname + "===" + lastname;
}
}
测试代码:package p.client;
import p.User;
public class ThreadLocalTest {
private static ThreadLocal<User> local = new ThreadLocal<User>();
private static User user;
/**
* @param args
*/
public static void main(String[] args) {
user = new User();
user.setFirstname("Green");
user.setLastname("Tom");
local.set(user);
new Thread(new Runnable() {
@Override
public void run() {
user.setFirstname("Red");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setLastname("Jim");
}
}).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = local.get();
System.out.println(user);
}
}
输出:Red===Tom
从结果得知,firstname已被另外开启的线程修改。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保存对象的方法,当我们在当前线程的其他地方需要访问此对象时,无需将其作为参数传递。
下面来看一个hibernate中典型的ThreadLocal的应用:
private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();
public static Session getSession() throws InfrastructureException {
Session s = threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
有时候,我们需要在Action中关闭Hibernate的Session,有了ThreadLocal,就不必将Session从Dao传递到Action,而可以从Action中直接拿到当前线程的Session。