美文网首页
深度剖析ThreadLocal

深度剖析ThreadLocal

作者: 掩流年 | 来源:发表于2019-12-09 23:40 被阅读0次

    概念

    ThreadLocal是并发编程中的一种对象共享方式,从字面意思上看,我们大概能推断出它的用法是作为线程局部变量来使用的。它的好处是,可以存有每个线程独立的数据而互不影响。可以简单的把ThreadLocal理解为一个类似于HashMap的数据结构。更准确的说是WeakHashMap。可以简单来写一个简化版的ThreadLocal

    class SimpleThreadLocal {
        static Map<String, String> map = new ConcurrentHashMap<>();
        private static CountDownLatch latch = new CountDownLatch(5);
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 5; i++) {
                int value = i;
                new Thread(() -> {
                    map.put(Thread.currentThread().getName(), Integer.toString(value));
                    latch.countDown();
                }).start();
            }
            latch.await();
            System.out.println(map);
        }
    }
    

    这就可以被作为一个ThreadLocal来使用了,在这个map中,key是它的名称,value可以作为每个线程独有的数据来存储。但实际上ThreadLocal却不是这么的简单,但也复杂不了多少。

    深度解析

    接下来看一段源码吧

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
       void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    上述展示了ThreadLocalput方法源码,看起来基本上是一目了然的,基于我们之上手写的ThreadLocal来说,不同的点在于,源码的map是线程独有的,而我们的map是共享的。这时候就要问自己一个问题,为什么要这么设计呢?
    答案是,如果采用共享的map,当某个线程被销毁的后,它的数据会占用内存不能被回收。这样导致了很多垃圾数据的积累。所以使用单个线程自带map存储数据是相当方便的。

    这时候又出现了新的问题,当我们使用线程池的时候,线程池中的核心线程不会被销毁,有时候线程会把较大的对象存入结构中,长时间的积累将导致内存泄漏。

    避免的方式是,ThreadLocal提供了remove()接口,对于不需要的对象,要及时调用remove()方法去清理。

    再来看下get()方法的源码

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    get()的逻辑从表面看来是个相当容易理解的,重点的深入到ThreadLocalMap中看看getMap的实现。

        static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    上述代码提供了ThreadLocalMap中的一个方法,我们能发现在其中有一个什么精巧的设计。我们可以把k当作线程实例,v当作值。整个Entry继承了WeakReference<ThreadLocal<?>>弱引用,弱引用的方式就是在Java虚拟机GC发现的时候,就可以立即回收。这样当外部的ThreadLocal强引用被回收的时候,这个Entry的key就会被置为null。

    应用与注意事项

    我们简单举个例子

       private static ThreadLocal<Connection> threadLocal = ThreadLocal.withInitial(() -> {
            try {
                return DriverManager.getConnection("url");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        });
    

    可以很好的想到,ThreadLocal可以做数据库的连接池。或者web端的session等等。它的作用可以说是十分广泛。
    在《Java高并发程序设计》这本书中,有个例子验证了共享一个变量和使用ThreadLocal对性能的影响,有兴趣可以读一读,条件以及得出的结论是:

    条件:
    4个线程
    一千万的随机数循环
    
    结论:
    多线程共享一个Random 耗时:`13s`
    使用ThreadLocal 耗时:`1.7s`
    

    另一方面,我们需要注意的是,ThreadLocal相当于全局变量,如果滥用它将会导致代码的可重用性降低以及增加耦合度,这是格外需要注意的。

    相关文章

      网友评论

          本文标题:深度剖析ThreadLocal

          本文链接:https://www.haomeiwen.com/subject/paaigctx.html