Java多线程下 ThreadLocal 的应用实例

ThreadLocal很容易让人望文生义,想固然地认为是一个“本地线程” 。其实,ThreadLocal并非一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable更容易让人理解一些。当使用 ThreadLocal 维护变量时,ThreadLocal 为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。 
 
      首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,通常状况下,经过ThreadLocal.set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的。各个线程中访问的是不一样的对象。 
 
      另外,说ThreadLocal使得各线程可以保持各自独立的一个对象,并非经过ThreadLocal.set()来实现的,而是经过每一个线程中的new 对象 的操做来建立的对象,每一个线程建立一个,不是什么对象的拷贝或副本。经过ThreadLocal.set()将这个新建立的对象的引用保存到各线程的本身的一个map中,每一个线程都有这样一个map,执行ThreadLocal.get()时,各线程从本身的map中取出放进去的对象,所以取出来的是各自本身线程中的对象,ThreadLocal实例是做为map的key来使用的。 
 
      若是ThreadLocal.set()进去的东西原本就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题。 

JDK 5 之后提供了泛型支持,ThreadLocal 被定义为支持泛型: 
public class ThreadLocal<T> extends Object 
T 为线程局部变量的类型。该类定义了 4 个方法: 
     1) protected T initialValue(): 返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get()  方法访问变量时将调用此方法,但若是线程以前调用了 set(T)  方法,则不会对该线程再调用 initialValue  方法。一般,此方法对每一个线程最多调用一次,但若是在调用 get() 后又调用了 remove(),则可能再次调用此方法。  该实现返回 null;若是程序员但愿线程局部变量具备 null  之外的值,则必须为 ThreadLocal 建立子类,并重写此方法。一般将使用匿名内部类完成此操做。 
     2)public T get():返回此线程局部变量的当前线程副本中的值。若是变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。 
     3)public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不须要重写此方法,它们只依靠 initialValue()  方法来设置线程局部变量的值。 

     4)public void remove():移除此线程局部变量当前线程的值。若是此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其 initialValue()  方法从新初始化其值。这将致使在当前线程屡次调用 initialValue  方法。  html

下面是一个使用 ThreadLocal 的例子,每一个线程产生本身独立的序列号。就是使用ThreadLocal存储每一个线程独立的序列号复本,线程之间互不干扰。 
package sync;  
public class SequenceNumber {  
  // 定义匿名子类建立ThreadLocal的变量  
  private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
   // 覆盖初始化方法  
   public Integer initialValue() {  
      return 0;  
  }  
 };  
  // 下一个序列号  
  public int getNextNum() {  
   seqNum.set(seqNum.get() + 1);  
   return seqNum.get();  
 }  
  private static class TestClient extends Thread {  
   private SequenceNumber sn;  
   public TestClient(SequenceNumber sn) {  
    this.sn = sn;  
  }  
   // 线程产生序列号  
   public void run() {  
    for (int i = 0; i < 3; i++) {  
    System.out.println("thread[" + Thread.currentThread().getName()   + "] sn[" + sn.getNextNum() + "]");  
   }  
  }  
 }  
  /** 
  * @param args 
  */  
  public static void main(String[] args) {  
  SequenceNumber sn = new SequenceNumber();  
      // 三个线程产生各自的序列号  
     TestClient t1 = new TestClient(sn);  
     TestClient t2 = new TestClient(sn);  
     TestClient t3 = new TestClient(sn);  
     t1.start();  
     t2.start();  
     t3.start();  
 }  
}
程序的运行结果以下:
thread[Thread-1] sn[1]  
thread[Thread-1] sn[2]  
thread[Thread-1] sn[3]  
thread[Thread-2] sn[1]  
thread[Thread-2] sn[2]  
thread[Thread-2] sn[3]  
thread[Thread-0] sn[1]
thread[Thread-0] sn[2]  
thread[Thread-0] sn[3]


从运行结果能够看出,使用了 ThreadLocal 后,每一个线程产生了独立的序列号,没有相互干扰。一般咱们经过匿名内部类的方式定义 ThreadLocal的子类,提供初始的变量值。 

        ThreadLocal和线程同步机制相比有什么优点呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 
 
        在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析何时对变量进行读写,何时须要锁定某个对象,何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。 而 ThreadLocal 则从另外一个角度来解决多线程的并发访问。ThreadLocal 会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每个线程都拥有本身的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,能够把不安全的变量封装进 ThreadLocal。 
 
        归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。 
 
        须要注意的是 ThreadLocal 对象是一个本质上存在风险的工具,应该在彻底理解将要使用的线程模型以后,再去使用 ThreadLocal 对象。这就引出了线程池(thread pooling)的问题,线程池是一种线程重用技术,有了线程池就没必要为每一个任务建立新的线程,一个线程可能会屡次使用,用于这种环境的任何 ThreadLocal 对象包含的都是最后使用该线程的代码所设置的状态,而不是在开始执行新线程时所具备的未被初始化的状态。 那么 ThreadLocal 是如何实现为每一个线程保存独立的变量的副本的呢?经过查看它的源代码,咱们会发现,是经过把当前“线程对象”看成键,变量做为值存储在一个 Map 中。  java

private T setInitialValue() {  
        T value = initialValue();  
        Thread t = Thread.currentThread();  
        ThreadLocalMap map = getMap(t);  
        if (map != null)  
            map.set(this, value);  
        else  
            createMap(t, value);  
        return value;  
}

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。概括了两点: 
         一、每一个线程中都有一个本身的ThreadLocalMap类对象,能够将线程本身的对象保持到其中,各管各的,线程能够正确的访问到本身的对象。 
         二、将一个共用的ThreadLocal静态实例做为key,将不一样对象的引用保存到不一样线程的ThreadLocalMap中,而后在线程执行的各处经过这个静ThreadLocal实例的get()方法取得本身线程保存的那个对象,避免了将这个对象做为参数传递的麻烦。  程序员


Synchronized仍是ThreadLocal?  web

   ThreadLocal以空间换取时间,提供了一种很是简便的多线程实现方式。由于多个线程并发访问无需进行等待,因此使用ThreadLocal 会得到更大的性能。虽然使用ThreadLocal会带来更多的内存开销,但这点开销是微不足道的。由于保存在ThreadLocal中的对象,一般都是比较小的对象。另外使用ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

   ThreadLocal和Synchonized都用于解决多线程并发访问。可是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每个线程都提供了变量的副本,使得每一个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通讯时可以得到数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

固然ThreadLocal并不能替代synchronized,它们处理不一样的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。 安全

参考: 多线程

http://my.oschina.net/huangyong/blog/159725
http://my.oschina.net/huangyong/blog/159489
http://www.itokit.com/2012/0817/74676.html
http://www.java3z.com/cwbwebhome/article/article20/200026.html?id=4841 并发

相关文章
相关标签/搜索