Java高并发(六)——ThreadLocal为线程保驾护航

释放双眼,带上耳机,听听看~!

       前边我们讲述多线程交互,多线程引起的安全问题,多线程安全的问题解决同步(synchronized、lock、CAS)……这一切的一切起源就是共享资源,共享临界区的数据安全引起的。那我们从另外一个角度想想呢?每个线程有自己的一份数据,是不是就会避免共享资源的数据问题了?ThreadLocal就是从这个角度出发而产生的,好,下边我们重点看看这个东东。

       一,简单使用:ThreadLocal是线程的局部变量,也就是说每个线程有自己单独的一块存独享数据的空间。属于线程的私有财产。好看下简单使用:


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
1public class ThreadLocalTest {
2    //1,线程不安全
3    //2,加锁控制
4    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
5    //3使用ThreadLocal容器
6    public static ThreadLocal<SimpleDateFormat> ts = new ThreadLocal<SimpleDateFormat>();
7
8    public static class ParseDate implements Runnable{
9        int i=0;
10        public ParseDate(int i ){
11            this.i=i;
12        }
13        @Override
14        public void run() {
15            try {
16                //线程不安全
17                //Date t = sdf.parse("2018-12-09 12:29:" + i%60);
18
19                //通过threadocal人手一个SimpleDateFormat
20                if(ts.get()==null){
21                    ts.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
22                }
23                Date t = ts.get().parse("2018-12-09 12:29:" + i%60);
24                System.out.println(i + ":" + t);
25            } catch (ParseException e) {
26                e.printStackTrace();
27            }
28        }
29    }
30
31    public static void main(String[] args) {
32        ExecutorService es = Executors.newFixedThreadPool(10);
33
34        for (int i = 0; i < 1000; i++) {
35            es.execute(new ParseDate(i));
36        }
37    }
38}
39

       二**,实现原理(源码分析)**:

       1,set方法:


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
1    /**
2     * 一,set方法
3     * Sets the current thread's copy of this thread-local variable
4     * to the specified value.  Most subclasses will have no need to
5     * override this method, relying solely on the {@link #initialValue}
6     * method to set the values of thread-locals.
7     *
8     * @param value the value to be stored in the current thread's copy of
9     *        this thread-local.
10     */
11    public void set(T value) {
12        Thread t = Thread.currentThread();
13        //1,获取当前线程的ThreadLocalMap
14        ThreadLocalMap map = getMap(t);
15        if (map != null)
16            map.set(this, value);
17        else
18            //2,为当前线程创建一个ThreadLocalMap
19            createMap(t, value);
20    }
21    
22    /**
23     * 1,getMap(t)
24     * Get the map associated with a ThreadLocal. Overridden in
25     * InheritableThreadLocal.
26     *
27     * @param  t the current thread
28     * @return the map
29     */
30    ThreadLocalMap getMap(Thread t) {
31        return t.threadLocals;
32    }
33
34    /**
35     * 2,createMap(t, value);
36     * Create the map associated with a ThreadLocal. Overridden in
37     * InheritableThreadLocal.
38     *
39     * @param t the current thread
40     * @param firstValue value for the initial entry of the map
41     */
42    void createMap(Thread t, T firstValue) {
43        t.threadLocals = new ThreadLocalMap(this, firstValue);
44    }
45
46
47    /**
48     * ThreadLocalMap为ThreadLocal的一个内部类
49     * ThreadLocalMap is a customized hash map suitable only for
50     * maintaining thread local values. No operations are exported
51     * outside of the ThreadLocal class. The class is package private to
52     * allow declaration of fields in class Thread.  To help deal with
53     * very large and long-lived usages, the hash table entries use
54     * WeakReferences for keys. However, since reference queues are not
55     * used, stale entries are guaranteed to be removed only when
56     * the table starts running out of space.
57     */
58    static class ThreadLocalMap {}
59

       2,get方法:这里注意一下其中的弱应用,方便垃圾回收,看一下弱引用的文章:https://www.cnblogs.com/absfree/p/5555687.html,https://blog.csdn.net/zmx729618/article/details/54093532


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
1    /**
2     * 二,get方法
3     * Returns the value in the current thread's copy of this
4     * thread-local variable.  If the variable has no value for the
5     * current thread, it is first initialized to the value returned
6     * by an invocation of the {@link #initialValue} method.
7     *
8     * @return the current thread's value of this thread-local
9     */
10    public T get() {
11        Thread t = Thread.currentThread();
12        ThreadLocalMap map = getMap(t);
13        if (map != null) {
14            //1,Entry为ThreadLocalMap内部类,弱应用
15            ThreadLocalMap.Entry e = map.getEntry(this);
16            if (e != null) {
17                @SuppressWarnings("unchecked")
18                T result = (T)e.value;
19                return result;
20            }
21        }
22        //2,如果不存在,则进行初始化
23        return setInitialValue();
24    }
25
26
27        /**
28         * 1,Entry
29         * The entries in this hash map extend WeakReference, using
30         * its main ref field as the key (which is always a
31         * ThreadLocal object).  Note that null keys (i.e. entry.get()
32         * == null) mean that the key is no longer referenced, so the
33         * entry can be expunged from table.  Such entries are referred to
34         * as "stale entries" in the code that follows.
35         */
36        static class Entry extends WeakReference<ThreadLocal<?>> {
37            /** The value associated with this ThreadLocal. */
38            Object value;
39
40            Entry(ThreadLocal<?> k, Object v) {
41                super(k);
42                value = v;
43            }
44        }
45
46
47    /**
48     * 2,初始化map
49     * Variant of set() to establish initialValue. Used instead
50     * of set() in case user has overridden the set() method.
51     *
52     * @return the initial value
53     */
54    private T setInitialValue() {
55        T value = initialValue();
56        Thread t = Thread.currentThread();
57        ThreadLocalMap map = getMap(t);
58        if (map != null)
59            map.set(this, value);
60        else
61            createMap(t, value);
62        return value;
63    }
64
65    /**
66     * JDK默认的初始化值为null,当然如果我们想修改,则继承进行method overridden即可
67     * Returns the current thread's "initial value" for this
68     * thread-local variable.  This method will be invoked the first
69     * time a thread accesses the variable with the {@link #get}
70     * method, unless the thread previously invoked the {@link #set}
71     * method, in which case the {@code initialValue} method will not
72     * be invoked for the thread.  Normally, this method is invoked at
73     * most once per thread, but it may be invoked again in case of
74     * subsequent invocations of {@link #remove} followed by {@link #get}.
75     *
76     * <p>This implementation simply returns {@code null}; if the
77     * programmer desires thread-local variables to have an initial
78     * value other than {@code null}, {@code ThreadLocal} must be
79     * subclassed, and this method overridden.  Typically, an
80     * anonymous inner class will be used.
81     *
82     * @return the initial value for this thread-local
83     */
84    protected T initialValue() {
85        return null;
86    }
87

       3,何时GC这些线程私有对象:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1    /**
2     * 一,线程退出时
3     * This method is called by the system to give a Thread
4     * a chance to clean up before it actually exits.
5     */
6    private void exit() {
7        if (group != null) {
8            group.threadTerminated(this);
9            group = null;
10        }
11        /* Aggressively null out all reference fields: see bug 4006245 */
12        target = null;
13        /* Speed the release of some of these resources */
14        threadLocals = null;
15        inheritableThreadLocals = null;
16        inheritedAccessControlContext = null;
17        blocker = null;
18        uncaughtExceptionHandler = null;
19    }
20

       可以看出当exit()时,Thread才会进行各种(包括ThreadLocalMap)清理工作。因此如果我们使用Thread Pool,Thread未必exit(),所以ThreadLocalMap就一直存在,而在使用中如果我们将一下大的对象set进入,就非常容易造成内存泄漏的情况。这就需要我们手动进行清除了,ThreadLocal帮我们提供了方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1    /**
2     * 二,调用ThreadLocal的remove方法
3     * Removes the current thread's value for this thread-local
4     * variable.  If this thread-local variable is subsequently
5     * {@linkplain #get read} by the current thread, its value will be
6     * reinitialized by invoking its {@link #initialValue} method,
7     * unless its value is {@linkplain #set set} by the current thread
8     * in the interim.  This may result in multiple invocations of the
9     * {@code initialValue} method in the current thread.
10     *
11     * @since 1.5
12     */
13     public void remove() {
14         ThreadLocalMap m = getMap(Thread.currentThread());
15         if (m != null)
16             m.remove(this);
17     }
18

       三,使用性能,其实很明显的使用自己私有的变量可能比使用公共的变量性能会好很多,这里引入一个Future的例子,了解一下,重点看下,使用ThreadLocal和不适用的性能差距……


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
1public class ThreadLocalHSTest {
2
3    public static final int Get_COUNT = 1000000;
4    public static final int THREAD_COUNT = 4;
5    static ExecutorService es = Executors.newFixedThreadPool(THREAD_COUNT);
6
7    public static Random rd =new Random(123);
8
9    public static ThreadLocal<Random> trd = new ThreadLocal<Random>(){
10        @Override
11        protected Random initialValue() {
12            return new Random(123);
13        }
14    };
15
16    public static class RndTask implements Callable<Long>{
17        private int mode =0;
18        public RndTask(int mode){
19            this.mode = mode;
20        }
21
22        public Random getRandom(){
23            if(mode==0){
24                return rd;
25            }else if(mode ==1 ){
26                return trd.get();
27            }else{
28                return  null;
29            }
30
31        }
32
33        @Override
34        public Long call() throws Exception {
35            long b = System.currentTimeMillis();
36            for (int i = 0; i < Get_COUNT; i++) {
37                getRandom().nextInt();
38            }
39
40            long e= System.currentTimeMillis();
41            System.out.println(Thread.currentThread().getName() + "spend" + (e-b) + "ms");
42            return e-b;
43        }
44    }
45
46    public static void main(String[] args) throws ExecutionException, InterruptedException {
47        Future<Long>[] future = new Future[Get_COUNT];
48        for (int i = 0; i < THREAD_COUNT; i++) {
49            future[i] = es.submit(new RndTask(0));
50        }
51
52        long totalTime = 0;
53        for (int i = 0; i < THREAD_COUNT; i++) {
54            totalTime +=future[i].get();
55        }
56        System.out.println("多线程访问同一个Random实例:" + totalTime + "ms");
57
58
59        for (int i = 0; i < THREAD_COUNT; i++) {
60            future[i] = es.submit(new RndTask(1));
61        }
62        totalTime = 0;
63        for (int i = 0; i < THREAD_COUNT; i++) {
64            totalTime +=future[i].get();
65        }
66        System.out.println("使用ThreadLocal包装Random实例:" + totalTime + "ms");
67    }
68}
69
70
71pool-1-thread-2spend311ms
72pool-1-thread-4spend330ms
73pool-1-thread-1spend331ms
74pool-1-thread-3spend331ms
75多线程访问同一个Random实例:1303ms
76pool-1-thread-3spend28ms
77pool-1-thread-2spend31ms
78pool-1-thread-1spend30ms
79pool-1-thread-4spend31ms
80使用ThreadLocal包装Random实例:120ms
81

       ThreadLocal,对于线程保存自己的变量,在其生命周期中进行使用是非常方便,但是要注意线程池中的使用,以及ThreadLocalMap的垃圾回收,防止出现内存溢出……       

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Jenkins高级篇之Pipeline技巧篇-4-根据参数传入条件控制执行不同stage

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索