关于ThreadLocal类的理解

在多线程的环境下,共享资源的访问就变得不安全了,ThreadLocal类可以保证每个线程使用的都是共享资源的一个副本,从而保证线程之间的资源都是独立的,确保资源的线程安全

源码片段(带官方注释):

 1 /**
 2      * Returns the current thread's "initial value" for this
 3      * thread-local variable.  This method will be invoked the first
 4      * time a thread accesses the variable with the {@link #get}
 5      * method, unless the thread previously invoked the {@link #set}
 6      * method, in which case the <tt>initialValue</tt> method will not
 7      * be invoked for the thread.  Normally, this method is invoked at
 8      * most once per thread, but it may be invoked again in case of
 9      * subsequent invocations of {@link #remove} followed by {@link #get}.
10      *
11      * <p>This implementation simply returns <tt>null</tt>; if the
12      * programmer desires thread-local variables to have an initial
13      * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
14      * subclassed, and this method overridden.  Typically, an
15      * anonymous inner class will be used.
16      *
17      * @return the initial value for this thread-local
18 */
19 protected T initialValue() {
20         return null;
21 }
1. 当线程第一次调用get()并且之前没有调用过set()方法时,就会调用此方法
2. 若调用了remove()方法,则下次调用get()方法时也会调用此方法
3. 一般需要重写此方法
 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); //获取当前线程的ThreadLocalMap属性
12         if (map != null) {
13             ThreadLocalMap.Entry e = map.getEntry(this); //从ThreadLocalMap中根据key(ThreadLocal对象)获取资源副本
14             if (e != null)
15                 return (T)e.value;
16         }
17         return setInitialValue(); //没值,则初始化资源副本
18     }
19 
20 /**
21      * Get the map associated with a ThreadLocal. Overridden in
22      * InheritableThreadLocal.
23      *
24      * @param  t the current thread
25      * @return the map
26  */
27     ThreadLocalMap getMap(Thread t) {
28         return t.threadLocals;
29     }
30 
31 /**
32      * Variant of set() to establish initialValue. Used instead
33      * of set() in case user has overridden the set() method.
34      *
35      * @return the initial value
36 */
37     private T setInitialValue() {
38         T value = initialValue();
39         Thread t = Thread.currentThread();
40         ThreadLocalMap map = getMap(t);
41         if (map != null)
42             map.set(this, value); //key: 当前的ThreadLocal对象  value: 资源副本
43         else
44             createMap(t, value);
45         return value;
46     }

 

 1 /**
 2      * Create the map associated with a ThreadLocal. Overridden in
 3      * InheritableThreadLocal.
 4      *
 5      * @param t the current thread
 6      * @param firstValue value for the initial entry of the map
 7      * @param map the map to store.
 8      */
 9     void createMap(Thread t, T firstValue) {
10         t.threadLocals = new ThreadLocalMap(this, firstValue);
11     }
1. 获取当前线程中保存的资源副本
2. Thread类里有一个threadLocals属性,由ThreadLocal类来维护,作用是存储ThreadLocal对象和资源副本的key-value键对值
  ThreadLocal.ThreadLocalMap threadLocals = 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     }

 

 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      * <tt>initialValue</tt> 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      }

 

总结:

  1. Thread类中有一个ThreadLocal.ThreadLocalMap threadLocals属性,供ThreadLocal来维护并且保存资源副本

  2. ThreadLocal保证线程安全的原理就是,在线程中保存一份共享资源的副本,保存的形式是key-value键值对(key是当前ThreadLocal对象,value是资源副本)

  3. 一个线程的threadLocals属性中可能有多个ThreadLocal对象,每个ThreadLocal对象又对应多个不同的资源

示例代码:

 1 public class Main {
 2     public static void main(String[] args){
 3         SquNum sn = new SquNum(); //多个线程使用同一个对象
 4 
 5         Thread t1 = new Thread(new ThreadTemp(sn));
 6         Thread t2 = new Thread(new ThreadTemp(sn));
 7         Thread t3 = new Thread(new ThreadTemp(sn));
 8 
 9         t1.start();
10         t2.start();
11         t3.start();
12 
13         System.out.println(sn.getNextNum());
14     }
15 }
16 
17 class SquNum{
18     private ThreadLocal<Common> seqNum = new ThreadLocal<Common>(){
19         @Override
20         protected Common initialValue() { //重写initialValue方法
21             return new Common();
22         }
23     };
24 
25     public int getNextNum(){
26         seqNum.get().setCount(seqNum.get().getCount() + 1); //调用ThreadLocal的get, set方法
27         seqNum.set(seqNum.get());
28         return seqNum.get().getCount();
29     }
30 }
31 
32 class ThreadTemp implements Runnable{
33     private SquNum sn;
34 
35     public ThreadTemp(SquNum sn) {
36         this.sn = sn;
37     }
38     @Override
39     public void run() {
40         for (int i = 0; i < 3; i++) {
41             System.out.println("Thread: [" + Thread.currentThread().getName() + "] sn: [" + sn.getNextNum() + "]");
42         }
43     }
44 }
45 
46 class Common{
47     private Integer count = 0;
48 
49     public Integer getCount() {
50         return count;
51     }
52 
53     public void setCount(Integer count) {
54         this.count = count;
55     }
56 }

 

输出:

Thread: [Thread-0] sn: [1]
Thread: [Thread-0] sn: [2]
Thread: [Thread-0] sn: [3]
Thread: [Thread-1] sn: [1]
Thread: [Thread-1] sn: [2]
Thread: [Thread-1] sn: [3]
1
Thread: [Thread-2] sn: [1]
Thread: [Thread-2] sn: [2]
Thread: [Thread-2] sn: [3]

 

解释:

  1. 从输出可以看到,每一个线程(包括主线程)使用的Common对象资源没有相互影响

  2. 上边代码中只new了一个ThreadLocal对象,所有线程共用一个ThreadLocal对象,也就是说,所有线程中的ThreadLocalMap中的key都是同一个ThreadLocal对象

  3. 虽然共享同一个ThreadLocal对象,但资源并不会共享,因为在通过sn.getNextNum()时,会调用ThreadLocal的seqNum.get()方法,此时在当前线程中将ThreadLocal对象作为key在ThreadLocalMap对象里查找value,发现value为空,就会调用initialValue(),重新new一个Common对象,所以资源并不会共享。

  4. 若需要线程之间资源访问的安全,则需要在线程中通过ThreadLocal来访问资源,并保证重写了ThreadLocal的initialValue()方法,此方法返回一个新的资源副本。

 

 

 

 

 

 

 

posted @ 2015-12-29 20:55  桦沐  阅读(340)  评论(0编辑  收藏  举报