关于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()方法,此方法返回一个新的资源副本。