按照Java API的介绍,ThreadLocal类提供了线程独立的变量。可以理解为Thread Local Variable。内部实现是一个Map。Map中的Key为线程对象,Value则通过Set方法设置。每个线程只能通过get方法得到本线程的ThreadLocal 变量。因此线程之间的变量完全分隔开来。参考一段Hibernate中获取Session的方法。
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>(); //ThreadLocal 声明
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession()
: null;
threadLocal.set(session);
}
return session;
}
可以看到在获取session时,首先到threadLocal对象中获取。如果session为null,再通过sessionFactory新建session对象,用set方法设置到threadLocal中,返回session对象。这样,当同一个线程多次访问getSession()方法时,返回的是同一个session对象。
再看一段ThreadLocal的错误使用方法。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadLocalTest {
private static ThreadLocal<String> tl = new ThreadLocal<String>(); //ThreadLocal变量tl
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
//启动两个线程
exec.execute(new sayHello("11"));
exec.execute(new sayHello("hi"));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String get(){
return tl.get();
}
public static void set(String aa){
tl.set(aa);
}
}
class sayHello implements Runnable{
public String w;
public sayHello(String word){
w = word;
ThreadLocalTest.set(w); //设置
}
public void run(){
try{
while(true){
System.out.println(ThreadLocalTest.get()); //通过get方法获得值
TimeUnit.SECONDS.sleep(1);
}
}catch(InterruptedException ea){
System.out.println("Exit from exception");
}finally{
System.out.println("finally");
}
}
}
程序新建了两个线程,并打印ThreadLocal的值,其中ThreadLocal中的值在初始化时被设置。打印的结果为
null
null
null
null
...
总结原因为线程在初始化时,是通过main方法的线程初始化的,所以调用sayHello初始化方法的线程仍然是主线程,设置的是main方法进程的ThreadLocal变量,在线程启动后,run方法被子线程调用,get的是子线程的ThreadLocal变量,因此获得了不同的值。由于子线程中并没有初始化变量,所以得到的是null。
因此应在线程启动后,在run方法中使用set方法。下面是正确的例子。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadLocalTest {
private static ThreadLocal<String> tl = new ThreadLocal<String>();
public static String a = "111";
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new sayHello("11"));
exec.execute(new sayHello("hi"));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
exec.shutdown();
}
public static String get(){
return tl.get();
}
public static void set(String aa){
System.out.println(Thread.currentThread());
tl.set(aa);
}
}
class sayHello implements Runnable{
public String w;
public sayHello(String word){
w = word;
}
public void run(){
ThreadLocalTest.set(w); //在此处设置
try{
while(true){
System.out.println(ThreadLocalTest.get());
TimeUnit.SECONDS.sleep(1);
}
}catch(InterruptedException ea){
System.out.println("Exit from exception");
}finally{
System.out.println("finally");
}
}
}