介绍
我之前在任何场合都没有使用过thread local,因此没有注意到它,直到最近用到它的时候。
前提信息
线程可以理解为一个单独的进程,它有自己的调用栈。在java中每一个线程都有一个调用栈或者说每一个调用栈都有一个线程,即使你不在你的程序中创建线程,线程仍然会在你不知道的情况下运行。最简单的例子就是,当你通过main方法启动一个简单的java程序时,你不在程序里调用new Thread().start(),但是JVM仍然会创建一个main thread 去运行main方法。
main线程有一些特殊,因为所有的其它线程都是在此线程中产生,当此线程运运完成,应用程序的生命周期也就结束了。
在一个web 应用server上一般都会有线程池,因为创建一个线程是一个开销相对比较大的。所有的Java EE(Weblogic,Glassfish,JBoss etc)都有自己的线程池,这就意味着线程池中的线程会根据需要增加或者减少,因此并不会针对每个请求都会创建一个线程,一些已经存在的线程可能会被复用。
理解Thread Local
为了更好的理解Thread local,这里我实现一个最简单的Thread Local.
package ccs.progest.javacodesamples.threadlocal.ex1; import java.util.HashMap;import java.util.Map; public class CustomThreadLocal { private static Map threadMap = new HashMap(); public static void add(Object object) { threadMap.put(Thread.currentThread(), object); } public static void remove(Object object) { threadMap.remove(Thread.currentThread()); } public static Object get() { return threadMap.get(Thread.currentThread()); } } 现在你可以在你的应用中任何时候调用CustomThreadLocal的add方法,这个方法所做的事是将当前线程作为key,object作为value添加到map中,从而达到与这个线程相关连。这个object可能是正在执行线程中任何地方你想访问的对象,或者是一个与当前线程保持关连且有许多次重用的复杂对象。
package ccs.progest.javacodesamples.threadlocal.ex1; public class ThreadContext { private String userId; private Long transactionId; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Long getTransactionId() { return transactionId; } public void setTransactionId(Long transactionId) { this.transactionId = transactionId; } public String toString() { return "userId:" + userId + ",transactionId:" + transactionId; } } 现在是时候使用ThreadContext了。我将会启动二个线程,在每个线程中都会添加一个ThreadContext实例,这个实例中信息将会传达给每个线程。
package ccs.progest.javacodesamples.threadlocal.ex1; public class ThreadLocalMainSampleEx1 { public static void main(String[] args) { new Thread(new Runnable() { public void run() { ThreadContext threadContext = new ThreadContext(); threadContext.setTransactionId(1l); threadContext.setUserId("User 1"); CustomThreadLocal.add(threadContext); //here we call a method where the thread context is not passed as parameter PrintThreadContextValues.printThreadContextValues(); } }).start(); new Thread(new Runnable() { public void run() { ThreadContext threadContext = new ThreadContext(); threadContext.setTransactionId(2l); threadContext.setUserId("User 2"); CustomThreadLocal.add(threadContext); //here we call a method where the thread context is not passed as parameter PrintThreadContextValues.printThreadContextValues(); } }).start(); }}
package ccs.progest.javacodesamples.threadlocal.ex1; public class PrintThreadContextValues { public static void printThreadContextValues(){ System.out.println(CustomThreadLocal.get()); }}
注意: CustomThreadLocal.add(threadContext)这一行代码将当前线程与ContextThread实例关连起来了。执行之后结果如下:
userId:User 1,transactionId:1
userId:User 2,transactionId:2
这怎么可能因为我们根本没有传参数给ThreadContext的userId,transactionId到 PrintThreadContextValues。
其实很简单,当调用CustomThreadLocal.get()时,它取的是所关联线程中的对象。
好了,现在让我们看一个真正的ThreadLocal类的例子(上面的CustomThreadLocal仅仅是为了理解ThreadLocal背后的基理,而ThreadLocal以最佳的方式优化了内存,所以非常快)。
package ccs.progest.javacodesamples.threadlocal.ex2; public class ThreadContext { private String userId; private Long transactionId; private static ThreadLocal threadLocal = new ThreadLocal(){ @Override protected ThreadContext initialValue() { return new ThreadContext(); } }; public static ThreadContext get() { return threadLocal.get(); } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Long getTransactionId() { return transactionId; } public void setTransactionId(Long transactionId) { this.transactionId = transactionId; } public String toString() { return "userId:" + userId + ",transactionId:" + transactionId; }} 如javadoc所述,ThreadLocal通常用private static 字段修饰。
package ccs.progest.javacodesamples.threadlocal.ex2; public class ThreadLocalMainSampleEx2 { public static void main(String[] args) { new Thread(new Runnable() { public void run() { ThreadContext threadContext = ThreadContext.get(); threadContext.setTransactionId(1l); threadContext.setUserId("User 1"); //here we call a method where the thread context is not passed as parameter PrintThreadContextValues.printThreadContextValues(); } }).start(); new Thread(new Runnable() { public void run() { ThreadContext threadContext = ThreadContext.get(); threadContext.setTransactionId(2l); threadContext.setUserId("User 2"); //here we call a method where the thread context is not passed as parameter PrintThreadContextValues.printThreadContextValues(); } }).start(); }}
package ccs.progest.javacodesamples.threadlocal.ex2; public class PrintThreadContextValues { public static void printThreadContextValues(){ System.out.println(ThreadContext.get()); }}
执行结果如下:
userId:User 1,transactionId:1
userId:User 2,transactionId:2
另一个非常有用的场合是当你有一个非线程安全的复杂对象的时候,一个最普遍的例子我发现是SimpleDateFormat。package ccs.progest.javacodesamples.threadlocal.ex4;
import java.text.SimpleDateFormat;import java.util.Date; public class ThreadLocalDateFormat { // SimpleDateFormat is not thread-safe, so each thread will have one private static final ThreadLocal formatter = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("MM/dd/yyyy"); } }; public String formatIt(Date date) { return formatter.get().format(date); }} 结论 ThreadLocal有许多用法,这里只列举了二个(我认为是所有用法中的一部分) *真正的每个线程的上下文,如用户ID或事务ID。 *每个线程实例性能。 原文链接:http://java.dzone.com/articles/understanding-concept-behind(部分作了修改)