ThreadLocal的用法
发布时间: 2017/9/30 11:32:37

1.代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.landon.mavs.example.concurrent;
    
import java.util.concurrent.ThreadLocalRandom;
    
/**
 *
 * ThreadLocal例子
 *
 * <pre>
 *     1.线程局部变量.其完全不提供锁.以空间换时间的手段,为每个线程提供变量的独立副本,以保证线程的安全.
 *     2.接口:
 *         public T get() 返回此线程局部变量的当前线程副本中的值
 *         public void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值
 *         public void remove() 移除此线程局部变量当前线程的值
 *         protected T initialValue() 返回此线程局部变量的当前线程的“初始值”
 *  3.源码:
 *       // 1.取得当前调用线程t 2.根据t获得ThreadLocalMap【Entry(ThreadLocal k, Object v)】(即每个线程都有一个threadLocals)
 *       // 3. 如果map为不null,则将this和value设置到map;否则创建map
 *       public void set(T value) {
 *         Thread t = Thread.currentThread();
 *         ThreadLocalMap map = getMap(t);
 *         if (map != null)
 *             map.set(this, value);
 *         else
 *             createMap(t, value);
 *     }
 *    
 *     // 1.取得当前调用线程t 2.取得t线程关联的ThreadLocalMap
 *     // 3.如果map不为null则将this作为key得到Entry.然后得到值.
 *     // 4.如果map为null则返回initialValue(默认为null,同时会将initialValue set至当前线程)
 *     public T get() {
 *         Thread t = Thread.currentThread();
 *         ThreadLocalMap map = getMap(t);
 *         if (map != null) {
 *             ThreadLocalMap.Entry e = map.getEntry(this);
 *             if (e != null)
 *                 return (T)e.value;
 *         }
 *         return setInitialValue();
 *     }.
 *  4.注意:
 *      1.不同线程间的对象副本并不是由ThreadLocal创建的.因为set方法未生成任何value的副本.所以将一个对象的实例设置到不同线程的ThreadLocal中,同样无法保证线程安全.
 *  所以需要每个线程内创建副本,然后设置到ThreadLocal中(即ThreadLocal只是一个key而已.).
 *      2.从get方法的最后一句看出,如果没有set,则第一次调用get方法时会调用initialValue;可使用匿名内部类覆写此方法,如果不希望初始值为null.另外如果调用了remove方法,即从
 *  ThreadLocalMap移除ThreadLocal key,则再次调用get方法时依然会调用initialValue.
 *      3.ThreadLocal实例通常是类中的 private static final字段(1.其只是用来做key,和当前线程绑定,所以用static方便 2.通常用静态方法直接获取局部变量实例,
 *  如ThreadLocalRandom),它们希望将状态与某一个线程相关联.
 
 *  5.总结:
 *      简单来说,ThreadLocal就是一个当前线程局部变量表的一个key,该key对应的V是new出来的一个新值,每个线程的都不一样.这样
 *  当前线程拿到ThreadLocal.get->则直接获得线程当前局部变量表对应的V.(第一次调用get时会将会将initialValue set,
 *  所以初始化ThreadLocal的时候可覆写initialValue).
 * </pre>
 *
 * @author landon
 *
 */
public class ThreadLocalExample {
    // 一个线程局部变量
    private static final ThreadLocal<ThreadLocalVar> localVar = newThreadLocal<>();
    
    // 线程局部变量内部的V
    private static class ThreadLocalVar {
        public int var;
    }
    
    // 初始化的时候指定了localVar2的变量值
    private static final ThreadLocal<ThreadLocalVar> localVar2 = newThreadLocal<ThreadLocalVar>() {
        protected ThreadLocalVar initialValue() {
            return new ThreadLocalVar();
        }
    };
    
    // 一个任务
    private static class ThreadLocalTask implements Runnable {
        @Override
        public void run() {
            // 注意这里必须要new一个值,然后set.->即set了当前调用线程中局部变量的值.
            ThreadLocalVar var new ThreadLocalVar();
            localVar.set(var);
    
            // 随机一个局部变量的值.这里用到了ThreadLocalRandom,一个活生生的例子
    
            // public static ThreadLocalRandom current() {
            // return localRandom.get();
            // }
    
            // 这里初始化了一个线程局部变量值,是new了一个ThreadLocalRandom.这个很关键.
            // private static final ThreadLocal<ThreadLocalRandom> localRandom =
            // new ThreadLocal<ThreadLocalRandom>() {
            // protected ThreadLocalRandom initialValue() {
            // return new ThreadLocalRandom();
            // }
            // };
            var.var = ThreadLocalRandom.current().nextInt();
    
            // 打印当前调用线程,即当前调用线程的线程局部变量的值.
            System.out.println("curThread:" + Thread.currentThread().getName()
                    " localVar:" + localVar.get().var);
    
            // 操作localVar2,将当前线程的id复制给localvar2
            localVar2.get().var = (int) Thread.currentThread().getId();
    
            System.out.println("curThread:" + Thread.currentThread().getName()
                    " localVar2:" + localVar2.get().var);
        }
    }
    
    // 从输出看,ThreadLocalExample#localVar如果不是线程局部变量则多线程操作时,一定要加锁的.但是如果是线程局部变量,则其只用来做一个key而已,局部变量的值是new出来
    // 并设置,所以每个线程的ThreadLocal的get值都是互相没有任何关系的。
    // 多线程之间可随意操作localVar.
    // 同理localvar2
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalTask(), "Thread-" + i).start();
        }
    
        // 主线程操作
        localVar.set(new ThreadLocalVar());
        localVar.get().var 100;
    
        System.out.println("curThread:" + Thread.currentThread().getName()
                " localVar:" + localVar.get().var);
    
        // 主线程操作localvar2
        localVar2.get().var 201;
    
        System.out.println("curThread:" + Thread.currentThread().getName()
                " localVar:" + localVar2.get().var);
    }
}