开发文章

java的ThreadLocal

 

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。

代码如下:

 

复制内容到剪贴板
  1. /**  
  2.     * Sets the current thread's copy of this thread-local variable  
  3.     * to the specified value.  Most subclasses will have no need to  
  4.     * override this method, relying solely on the {@link #initialValue}  
  5.     * method to set the values of thread-locals.  
  6.     *  
  7.     * @param value the value to be stored in the current thread's copy of  
  8.     *        this thread-local.  
  9.     */    
  10.    public void set(T value) {    
  11.        Thread t = Thread.currentThread();    
  12.        ThreadLocalMap map = getMap(t);    
  13.        if (map != null)    
  14.            map.set(this, value);    
  15.        else    
  16.            createMap(t, value);    
  17.    }    
  18. /**  
  19.  * Get the map associated with a ThreadLocal. Overridden in  
  20.  * InheritableThreadLocal.  
  21.  *  
  22.  * @param  t the current thread  
  23.  * @return the map  
  24.  */    
  25. ThreadLocalMap getMap(Thread t) {    
  26.     return t.threadLocals;    
  27. }    
  28. /**  
  29.  * Create the map associated with a ThreadLocal. Overridden in  
  30.  * InheritableThreadLocal.  
  31.  *  
  32.  * @param t the current thread  
  33.  * @param firstValue value for the initial entry of the map  
  34.  * @param map the map to store.  
  35.  */    
  36. void createMap(Thread t, T firstValue) {    
  37.     t.threadLocals = new ThreadLocalMap(this, firstValue);    
  38. }    
  39. /**  
  40.  * Returns the value in the current thread's copy of this  
  41.  * thread-local variable.  If the variable has no value for the  
  42.  * current thread, it is first initialized to the value returned  
  43.  * by an invocation of the {@link #initialValue} method.  
  44.  *  
  45.  * @return the current thread's value of this thread-local  
  46.  */    
  47. public T get() {    
  48.     Thread t = Thread.currentThread();    
  49.     ThreadLocalMap map = getMap(t);    
  50.     if (map != null) {    
  51.         ThreadLocalMap.Entry e = map.getEntry(this);    
  52.         if (e != null)    
  53.             return (T)e.value;    
  54.     }    
  55.     return setInitialValue();    
  56. }  

 

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

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

 

Java Code复制内容到剪贴板
  1. public class SequenceNumber {  
  2.     /**1.通过匿名内部类覆盖ThreadLocal 的initialValue() 方法 指定初始值 */  
  3.     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){  
  4.         public Integer initialValue() {  
  5.             return 0;  
  6.         }  
  7.     };  
  8.   
  9.     /**2. 获取下一个序列值*/  
  10.     public int getNextNum() {  
  11.         seqNum.set(seqNum.get() + 1);  
  12.         return seqNum.get();  
  13.     }  
  14.   
  15.     public static void main(String[] args) {  
  16.         SequenceNumber sn = new SequenceNumber();  
  17.   
  18.         /**3. 3个线程共享sn 各自产生序列号 */  
  19.         TestClient t1 = new TestClient(sn);  
  20.         TestClient t2 = new TestClient(sn);  
  21.         TestClient t3 = new TestClient(sn);  
  22.   
  23.         t1.start();  
  24.         t2.start();  
  25.         t3.start();  
  26.   
  27.     }  
  28.   
  29.     private static class TestClient extends Thread {  
  30.         private SequenceNumber sn;  
  31.   
  32.         public TestClient(SequenceNumber sn) {  
  33.             this.sn = sn;  
  34.         }  
  35.   
  36.         public void run() {  
  37.             /**4. 每个线程打出 3个序列值 */  
  38.             for (int i = 0; i < 3; i++) {  
  39.                 System.out.println("thread[" + Thread.currentThread().getName() + "]sn[" +sn.getNextNum() +"]");  
  40.   
  41.             }  
  42.         }  
  43.     }  
  44. }  

 

通常我们通过匿名内部类的方式定义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()方法的代码是对应。 

 

感谢 U的博客 支持 磐实编程网 原文地址:
blog.csdn.net/sinat_36517257/article/details/53507055

文章信息

发布时间:2016-12-10

作者:U的博客

发布者:aquwcw

浏览次数: