java ThreadLocal

来源:互联网 发布:怎么作曲编曲软件 编辑:程序博客网 时间:2024/06/05 08:15

ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复,每一个“线程对象”对应线程的“变量副本”,而到达了线程安全。

我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。

我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。

虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。

按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但Spring的DAO模板类并未采用线程同步机制,因为线程同步限制了并发访问,会带来很大的性能损失。

此外,通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现难度。那模板类究竟仰丈何种魔法神功,可以在无需同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal!

ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒。

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。

代码如下:

/**     * Sets the current thread's copy of this thread-local variable     * to the specified value.  Most subclasses will have no need to     * override this method, relying solely on the {@link #initialValue}     * method to set the values of thread-locals.     *     * @param value the value to be stored in the current thread's 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;  }  /**  * Create the map associated with a ThreadLocal. Overridden in  * InheritableThreadLocal.  *  * @param t the current thread  * @param firstValue value for the initial entry of the map  * @param map the map to store.  */  void createMap(Thread t, T firstValue) {      t.threadLocals = new ThreadLocalMap(this, firstValue);  }  /**  * Returns the value in the current thread's copy of this  * thread-local variable.  If the variable has no value for the  * current thread, it is first initialized to the value returned  * by an invocation of the {@link #initialValue} 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) {          ThreadLocalMap.Entry e = map.getEntry(this);          if (e != null)              return (T)e.value;      }      return setInitialValue();  }   

值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

public class SequenceNumber {    /**1.通过匿名内部类覆盖ThreadLocal 的initialValue() 方法 指定初始值 */    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){        public Integer initialValue() {            return 0;        }    };    /**2. 获取下一个序列值*/    public int getNextNum() {        seqNum.set(seqNum.get() + 1);        return seqNum.get();    }    public static void main(String[] args) {        SequenceNumber sn = new SequenceNumber();        /**3. 3个线程共享sn 各自产生序列号 */        TestClient t1 = new TestClient(sn);        TestClient t2 = new TestClient(sn);        TestClient t3 = new TestClient(sn);        t1.start();        t2.start();        t3.start();    }    private static class TestClient extends Thread {        private SequenceNumber sn;        public TestClient(SequenceNumber sn) {            this.sn = sn;        }        public void run() {            /**4. 每个线程打出 3个序列值 */            for (int i = 0; i < 3; i++) {                System.out.println("thread[" + Thread.currentThread().getName() + "]sn[" +sn.getNextNum() +"]");            }        }    }}通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子在1处所示。TestClient线程产生一组序列号, 在3处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:thread[Thread-1]sn[1]thread[Thread-2]sn[1]thread[Thread-0]sn[1]thread[Thread-2]sn[2]thread[Thread-1]sn[2]thread[Thread-2]sn[3]thread[Thread-0]sn[2]thread[Thread-1]sn[3]考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
   ThreadLocal    不是用来解决共享对象的多线程访问问题的   线程中的对象是该线程自己使用的对象   其他线程是不需要访问的,也访问不到的   各个线程中访问的是不同的对象

另外:

   以上面的为基础来想,如果说其他线程依然能够访问,那岂不是依旧有并发问题?答案是肯定的。  (当然,也可以这么做,但是并发问题就需要另行解决)   所以正确的使用情况下,不应该被自己以外的线程去使用,为什么呢?   (1)每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。    (2)将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦

其实,关键点在于:

ThreadLocal类实现了“为每个线程提供不同的变量拷贝”

那么重点来了:

    ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。值得注意的就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是我们要保存的对象。获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,set()方法的代码是对应。
2 0
原创粉丝点击