ThreadLocal的本质和应用分析

来源:互联网 发布:疯狂英语李阳 知乎 编辑:程序博客网 时间:2024/04/29 09:05

引言:  在Java的多线程编程中,竞争资源的同步是一个需要格外关注的问题。处理使用volatile和同步锁机制实现资源访问的一致性之外,还可以使用ThreadLocal来保存线程的私有变量,从而避免了竞争资源的产生。


1.  ThreadLocal是什么?

    ThreadLocal是服务于Thread的一种本地私有数据机制,threadlocalvariable(线程局部变量), 即为每一个使用该变量的线程都提供一个变量值的副本,与使用的线程绑定,每一个线程都可以独立地改变自己的副本,不会产生冲突。

      其在Thread处于活跃状态,并且threadLocal对象可用的情况下,就可以在其中存放数据。在线程被销毁之后,则ThreadLocal中使用的资源和内存同时也被回收。

     本质上,JVM通过ThreadLocal为多线程情况下,进行资源的访问提供了一种隔离机制,简化了编程的复杂度。

2.  ThreadLocal的用法

     2.1 ThreadLocal的主要方法

  •     ThreadLocal()   : 创建实例
  •     T get()  :返回当前线程中的值,如果未设置,则返回初始值
  •     void remove()  : 移除当前线程的值,并释放资源 
  •     void set(T value) :设置当前线程线程副本中的
  •     protected void initialValue():  用以在子类中被重写其初始值

      由此可以看出此线程局部变量只可以存放一个对象。


    2.2  ThreadLocal使用示例

      

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package org.test;  
  2.   
  3. public class ThreadLocalTest extends Thread {  
  4.     //必须的static final  
  5.     private final ThreadLocal<Student> mythreadLocal = new ThreadLocal<Student>();  
  6.       
  7.     private int age;  
  8.     public ThreadLocalTest(String name) {  
  9.         super(name);  
  10.     }  
  11.           
  12.     public ThreadLocalTest(String name, int age) {  
  13.         super(name);  
  14.         this.age = age;  
  15.     }  
  16.         
  17.     public static void main(String[] args) {  
  18.         ThreadLocalTest student1 = new ThreadLocalTest("thread-first",23);  
  19.         ThreadLocalTest student2 = new ThreadLocalTest("thread-second",30);  
  20.           
  21.         student1.start();  
  22.         student2.start();  
  23.     }  
  24.       
  25.     public void run() {  
  26.         testInfo();  
  27.     }  
  28.       
  29.     public void testInfo() {  
  30.         String currentThreadName = Thread.currentThread().getName();          
  31.         System.out.println(currentThreadName + " is running!");  
  32.           
  33.         Student stud1 = this.getLocalVariable();          
  34.         System.out.println("Student " + stud1.name + " is in age " + stud1.age + " in " + currentThreadName);  
  35.     }  
  36.       
  37.     public Student getLocalVariable() {                    
  38.         if (mythreadLocal.get() == null) {               
  39.             Student student = new Student(Thread.currentThread().getName() + "-student"this.age);  
  40.             mythreadLocal.set(student);  
  41.         }  
  42.         return mythreadLocal.get();  
  43.     }  
  44.       
  45.     public class Student {  
  46.         private String name;  
  47.         private int age;          
  48.         public Student(){}  
  49.           
  50.         public Student(String name, int age) {  
  51.             this.name = name;  
  52.             this.age = age;  
  53.         }  
  54.   
  55.         public String getName() {  
  56.             return name;  
  57.         }  
  58.   
  59.         public void setName(String name) {  
  60.             this.name = name;  
  61.         }  
  62.   
  63.         public int getAge() {  
  64.             return age;  
  65.         }  
  66.   
  67.         public void setAge(int age) {  
  68.             this.age = age;  
  69.         }        
  70.     }  
  71. }  
  从代码的运行结果来看,他们彼此之间没有什么相互的影响。
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Thread-1 is running!  
  2. Thread-0 is running!  
  3. Student LiSi is in age 30 in Thread-1  
  4. Student ZhangSan is in age 24 in Thread-0  

  注意:  这里的ThreadLocal按照JavaDoc文档的说明,应该声明为private static,因为它是在多个线程之间共享的一个数据结构,类似Map,以thread对象实例做为Key的。

3.   ThreadLocal和同步锁机制的对比分析

       概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

      同步机制利用所实现资源的同步访问,确保某一个时刻只有一个线程在访问资源;而ThreadLoca则规避了同步,让每一个线程有自己的一份副本。

      他们之间不能彼此替代,只是从不同的角度去解决线程访问资源的问题。threadLocal无法替代锁实现的资源共享,而锁也做不到可以提供给独立的线程实例资源。

4.   基于JVM的内存模型来分析ThreadLocal

      ThreadLocal本质上是一个类似Map的结构,以各个线程对象本身为Key,将其值存放进去。这个结构在所有的基于同一个线程类创建出来的线程中被共享所有,就是只有一个,单例的对象。  虽然,使用get()方法来读取其值,但是默认的是使用当前的thread对象做为Key来检索的。

      具体的实现机制可以参照源码分析。

5.   ThreadLocal的源码分析

    

 首先,来看看get()方法的源代码

     

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public T get() {  
  2.      Thread t = Thread.currentThread(); //当前线程对象  
  3.      ThreadLocalMap map = getMap(t);  //基于对象检索值  
  4.      if (map != null) {  
  5.          ThreadLocalMap.Entry e = map.getEntry(this);  
  6.          if (e != null)  
  7.              return (T)e.value;  
  8.      }  
  9.      return setInitialValue(); //如果为空,则设置初始值  
  10.  }  
   set()函数的源代码:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void set(T value) {  
  2.         Thread t = Thread.currentThread(); //线程对象实例  
  3.         ThreadLocalMap map = getMap(t);  
  4.         if (map != null)  
  5.             map.set(this, value); //设值  
  6.         else  
  7.             createMap(t, value); // 创建,设置  
  8.     }  

  基于以上的代码,我们可以清楚的发现,其实就是基于对象实例的Map实现。

6.  总结

    ThreadLocal存放的内容,不支持基本数据类型,只支持对象类型。用以存放线程私有的数据,以规避繁琐的同步机制下的资源共享。一般而言,少量数据是可以通过这种简便的方式而实现线程访问的。这些数据不涉及到线程之间的通信和共享问题。

参考文档

1.  http://lavasoft.blog.51cto.com/62575/51926/

2.  http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html


0 0
原创粉丝点击