ThreadLocal 多线程同步关键字

来源:互联网 发布:华讯网络奖金能拿多少 编辑:程序博客网 时间:2024/05/21 09:09

包括:

一. 什么是 ThreadLocal

二. ThreadLocal 类中的方法简介

三. 如何使用ThreadLocal

     3.1 3个线程共享一个对象,各自产生序列号

     3.2 例子:共享变量a, 使用ThreadLocal 和没有使用的区别

四. ThreadLocal 和同步机制的比较

五.参考


一.什么是ThreadLocal

       ThreadLocal并非一个线程,而是一个线程局部变量。它的作用就是为使用该变量的线程都提供一个变量值的副本,每个线程都可

以独立的改变自己的副本,而不会和其他线程的副本造成冲突。

       从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

       通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而

为多线程环境常出现的并发访问问题提供了一种隔离机制。

       ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素

的键为线程对象,而值对应线程的变量副本,由于Key值不可重复,每一个“线程对象”对应线程的“变量副本”,而到达了线程安全。

       概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。

前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。


Ps:附带  源码文档

[html] view plain copy
  1. * This class provides thread-local variables.  These variables differ from  
  2. * their normal counterparts in that each thread that accesses one (via its  
  3. * {@code get} or {@code set} method) has its own, independently initialized  
  4. * copy of the variable.  {@code ThreadLocal} instances are typically private  
  5. * static fields in classes that wish to associate state with a thread (e.g.,  
  6. * a user ID or Transaction ID)  

[html] view plain copy
  1. <p>Each thread holds an implicit reference to its copy of a thread-local  
  2. * variable as long as the thread is alive and the {@code ThreadLocal}  
  3. * instance is accessible; after a thread goes away, all of its copies of  
  4. * thread-local instances are subject to garbage collection (unless other  
  5. * references to these copies exist)  


二.ThreadLocal类中的方法简介

由上注释,可以看到,重点需要了解 get() 和 set() 方法:

2.1 get() 方法:

[java] view plain copy
  1. /** 
  2.  * Returns the value in the current thread's copy of this 
  3.  * thread-local variable.  If the variable has no value for the 
  4.  * current thread, it is first initialized to the value returned 
  5.  * by an invocation of the {@link #initialValue} method. 
  6.  * 
  7.  * @return the current thread's value of this thread-local 
  8.  */  
  9. public T get() {  
  10.     Thread t = Thread.currentThread();  
  11.     ThreadLocalMap map = getMap(t);  
  12.     if (map != null) {  
  13.         ThreadLocalMap.Entry e = map.getEntry(this);  
  14.         if (e != null) {  
  15.             @SuppressWarnings("unchecked")  
  16.             T result = (T)e.value;  
  17.             return result;  
  18.         }  
  19.     }  
  20.     return setInitialValue();  
  21. }  


该方法会返回变量的在该线程中的副本,如果没有的话,首先会调用 initialValue() 进行初始化。

从上代码分析:

1. 得到该线程: Thread t = Thread.currentThread();

2. 根据该线程 拿到一个 ThreadLocalMap。 ThreadLocalMap map = getMap(t);

3. 通过传统的 map 的 get() 方法拿到变量的在该线程中的副本( 该map 的 key 是一个 ThreadLocal 对象,value 是变量的在该线程中的副本)。


此时分析下 ThreadLocalMap:

[java] view plain copy
  1.     /** 
  2.      * ThreadLocalMap is a customized hash map suitable only for 
  3.      * maintaining thread local values. No operations are exported 
  4.      * outside of the ThreadLocal class. The class is package private to 
  5.      * allow declaration of fields in class Thread.  To help deal with 
  6.      * very large and long-lived usages, the hash table entries use 
  7.      * WeakReferences for keys. However, since reference queues are not 
  8.      * used, stale entries are guaranteed to be removed only when 
  9.      * the table starts running out of space. 
  10.      */  
  11.     static class ThreadLocalMap {  
  12.   
  13.         /** 
  14.          * The entries in this hash map extend WeakReference, using 
  15.          * its main ref field as the key (which is always a 
  16.          * ThreadLocal object).  Note that null keys (i.e. entry.get() 
  17.          * == null) mean that the key is no longer referenced, so the 
  18.          * entry can be expunged from table.  Such entries are referred to 
  19.          * as "stale entries" in the code that follows. 
  20.          */  
  21.         static class Entry extends WeakReference<ThreadLocal<?>> {  
  22.             /** The value associated with this ThreadLocal. */  
  23.             Object value;  
  24.   
  25.             Entry(ThreadLocal<?> k, Object v) {  
  26.                 super(k);  
  27.                 value = v;  
  28.             }  
  29.         }  
  30.         /** 
  31.          * The table, resized as necessary. 
  32.          * table.length MUST always be a power of two. 
  33.          */  
  34.         private Entry[] table;  
  35. ...  

        可以看到,ThreadLocalMap 相当于一个订制的 HashMap,key 为 ThreadLocal 对象,value 为该值,而且是一个静态类,这就意味着 各个变量都放在内存中。了解了这些,其实就可以自己实现自己的 ThreadLocal。

        Ps:此时可能会觉得 Key 是 Thread,实际上不是的,虽然第一步和 第二步看起来是 通过 Thread,但是其实 Thread 类中会维护了一个 ThreadLocalMap 的引用。

[java] view plain copy
  1. ThreadLocalMap getMap(Thread t) {  
  2.         return t.threadLocals;  
  3.     }  


2.2 set() 方法:
[java] view plain copy
  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. }  

参考 get 方法,知道了 其实现原理是通过 订制的 hashMap,那么set 方法就好理解了。


2.3 remove() 方法:

[java] view plain copy
  1. /** 
  2.  * Removes the current thread's value for this thread-local 
  3.  * variable.  If this thread-local variable is subsequently 
  4.  * {@linkplain #get read} by the current thread, its value will be 
  5.  * reinitialized by invoking its {@link #initialValue} method, 
  6.  * unless its value is {@linkplain #set set} by the current thread 
  7.  * in the interim.  This may result in multiple invocations of the 
  8.  * {@code initialValue} method in the current thread. 
  9.  * 
  10.  * @since 1.5 
  11.  */  
  12.  public void remove() {  
  13.      ThreadLocalMap m = getMap(Thread.currentThread());  
  14.      if (m != null)  
  15.          m.remove(this);  
  16.  }  
        也是采用了 hashmap 的 remove 的原理。Ps:如果 remove() 之后再 get() ,是不会报错的,此时会重新初始化一个 value。参考 get方法 最后的 setInitialValue().

至此,了解了 get(),set(),remove() 三个方法。


三. 如何使用ThreadLocal

3.1 3个线程共享一个对象,各自产生序列号

[java] view plain copy
  1. public class SequenceNumber {    
  2.       
  3.     //通过一个匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值    
  4.     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){    
  5.         public Integer initialValue(){    
  6.             return 0;    
  7.         }    
  8.     };    
  9.         
  10.     //获取下一个序列号    
  11.     public int getNextNum(){    
  12.         seqNum.set(seqNum.get() + 1);    
  13.         return seqNum.get();    
  14.     }    
  15.         
  16.     public static void main(String args[]){    
  17.             
  18.         SequenceNumber sn = new SequenceNumber();    
  19.             
  20.         ThreadClient t1 = new ThreadClient(sn);    
  21.         ThreadClient t2 = new ThreadClient(sn);    
  22.         ThreadClient t3 = new ThreadClient(sn);    
  23.             
  24.         t1.start();    
  25.         t2.start();    
  26.         t3.start();    
  27.     }    
  28.         
  29.     private static class ThreadClient extends Thread    
  30.     {    
  31.         private SequenceNumber sn;    
  32.             
  33.         public ThreadClient(SequenceNumber sn)  {  this.sn = sn;  }    
  34.             
  35.         public void run(){    
  36.             for(int i = 0; i < 3; ++i)    
  37.                 System.out.println("Thread[" + Thread.currentThread().getName() +    
  38.                         "] sn[" + sn.getNextNum() + "]");    
  39.         }    
  40.     }    
  41. }   

       那么,对于这个例子来说,首先是3个线程共享了SequenceNumber对象,那么如何才能保证用同一个东西,但是互不影响,这时候就用到了ThreadLocal来实现这个功能。ThreadLocal类中的Map,存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。所以,各个线程都是在各自的副本上做操作,互不影响。

概况说下上述代码流程:

  1. 首先在SequenceNumber类中创建ThreadLocal对象seqNum,用来保存线程间需要隔离处理的对象。
  2. 创建一个获取要隔离访问的数据的方法getXxx(),比如上述的public int getNextNum()方法,主要是里面的seqNum.get()方法。
  3. 在run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。

       可能会有个疑问:创建3个线程,这3个线程是什么时候存到ThreadLocal的Map里面的,而且是怎么存放的,get方法取出的时候怎么知道取哪一个。

       首先解决是什么时候这3个线程存放到Map里面的,其实是在上述代码调用public int getNextNum()中的seqNum.get()方法,也就是 在 setInitialValue() 的时候,当初始化发现没有该线程在 ThreadLocal 中的时候,则会 加入:

[java] view plain copy
  1. private T setInitialValue() {  
  2.         T value = initialValue();  
  3.         Thread t = Thread.currentThread();  
  4.         ThreadLocalMap map = getMap(t);  
  5.         if (map != null)  
  6.             map.set(this, value);  
  7.         else  
  8.             createMap(t, value);  
  9.         return value;  
  10.     }  


3.2 例子:共享变量a, 使用ThreadLocal 和没有使用的区别

[java] view plain copy
  1. public class Test {    
  2.     public static int a = 0;    
  3.         
  4.     public static void main(String args[])    
  5.     {    
  6.         MyThread myThread = new MyThread();    
  7.         myThread.start();    
  8.             
  9.         for(int i = 0; i < 5; ++i)    
  10.         {    
  11.             a = a + 1;    
  12.             System.out.println(Thread.currentThread().getName() + ":" + a);    
  13.         }    
  14.     }    
  15.         
  16.     public static class MyThread extends Thread    
  17.     {    
  18.         public void run()    
  19.         {    
  20.             for(int i = 0; i < 5; ++i)    
  21.             {    
  22.                 a = a + 1;    
  23.                 System.out.println(Thread.currentThread().getName() + ":" + a);    
  24.             }    
  25.         }    
  26.     }    
  27. }  

输出为:

[html] view plain copy
  1. main:2    
  2. main:3    
  3. main:4    
  4. main:5    
  5. main:6    
  6. Thread-0:2    
  7. Thread-0:7    
  8. Thread-0:8    
  9. Thread-0:9    
  10. Thread-0:10   

        此程序两个线程都使用了共享变量,但是没有使用相应的处理多线程问题的方法,所以会出现错误,这不难理解。以下是 经过 ThreadLocal处理的解决:

[java] view plain copy
  1. public class Test {    
  2.     
  3. private static ThreadLocal<Integer> a = new ThreadLocal<Integer>(){    
  4.     public Integer initialValue(){    
  5.         return 0;    
  6.     }    
  7. };    
  8.     
  9. public static void main(String args[])    
  10. {    
  11.     MyThread myThread = new MyThread();    
  12.     myThread.start();    
  13.         
  14.     for(int i = 0; i < 5; ++i)    
  15.     {    
  16.         a.set(a.get() + 1);    
  17.         System.out.println(Thread.currentThread().getName() + ":" + a.get());    
  18.     }    
  19. }    
  20.     
  21. public static class MyThread extends Thread    
  22. {    
  23.     public void run()    
  24.     {    
  25.         for(int i = 0; i < 5; ++i)    
  26.         {    
  27.             a.set(a.get() + 1);    
  28.             System.out.println(Thread.currentThread().getName() + ":" + a.get());    
  29.         }    
  30.     }    
  31. }   
输出为:

[html] view plain copy
  1. main:1    
  2. main:2    
  3. main:3    
  4. main:4    
  5. main:5    
  6. Thread-0:1    
  7. Thread-0:2    
  8. Thread-0:3    
  9. Thread-0:4    
  10. Thread-0:5   

四.ThreadLocal和同步机制的比较

       ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

       在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

        而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线 程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编 写多线程代码时,可以把不安全的变量封装进ThreadLocal。

       当 然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了 同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的 有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线 程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所 以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间 的共享冲突,可以使用ThreadLocal,这将极大地简化你的程 序,使程序更加易读、简洁。

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


五.参考

大多数内容都是以下3个网址的内容,然后对其中的一些例子加上自己的理解,有意见或者建议欢迎指出。

  1. http://lavasoft.blog.51cto.com/62575/51926/
  2. http://justsee.iteye.com/blog/791919
  3. http://blog.csdn.net/abing37/article/details/4460298
1 0
原创粉丝点击