ThreadLocal是什么
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
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。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法 也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
代码示例:
两个对象:
普通的对象
package basic.threadLocal; /** * Created with IntelliJ IDEA. * User: yfwangqing * Date: 13-7-26 * Time: 上午8:49 * To change this template use File | Settings | File Templates. */ public class Mybean { private int num; public int getNum() { return num; } public void add(int num) { this.num++; } }
使用ThreadLocal的对象
package basic.threadLocal; /** * Created with IntelliJ IDEA. * User: yfwangqing * Date: 13-7-26 * Time: 上午8:50 * To change this template use File | Settings | File Templates. */ public class MySafeBean { private ThreadLocal<Integer> localCount = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; } }; public Integer getNum() { return localCount.get(); } public void add(Integer num) { int count = localCount.get(); count++; this.localCount.set(count); } }
测试代码:
package basic.threadLocal; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Created with IntelliJ IDEA. * User: yfwangqing * Date: 13-7-26 * Time: 上午8:57 * To change this template use File | Settings | File Templates. */ public class ThreadLocalTest { private Mybean mybean; private MySafeBean mySafeBean; private Thread my1; private Thread my2; private Thread mySafe1; private Thread mySafe2; @Before public void setUp() throws Exception { mybean = new Mybean(); mySafeBean = new MySafeBean(); my1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { mybean.add(i); } System.out.println("m1:" + mybean.getNum()); } }); my2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { mybean.add(i); } System.out.println("m2:" + mybean.getNum()); } }); mySafe1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { mySafeBean.add(i); } System.out.println("s1:" + mySafeBean.getNum()); } }); mySafe2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { mySafeBean.add(i); } System.out.println("s2:" + mySafeBean.getNum()); } }); } @After public void tearDown() throws Exception { } @Test public void test() throws Exception { my1.start(); //不定值 my2.start(); //不定值 mySafe1.start(); //始终输出100 mySafe2.start(); //始终输出50 } }
对象回收方面:每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。所以在性能上ThreadLocal要优于同步方式。
总结: