• ThreadLocal 源码分析


    一、定义
    ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
    概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
    但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
     
    二、基本使用
    示例
    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        threadLocal.set(999);
        // 模拟3个线程
        for (int i=0; i<3; i++) {
            new Thread(()-> {
                for (int j = 0; j<2000; j++) {
                    if (null==threadLocal.get()) {
                        threadLocal.set(1);
                    } else {
                        threadLocal.set(threadLocal.get()+1);
                    }
                }
                System.out.println(Thread.currentThread().getName() + " " +  threadLocal.get());
            }).start();
        }
        System.out.println(Thread.currentThread().getName() + " " +  threadLocal.get());
    }
    

    输出结果:

    main 999
    Thread-0 2000
    Thread-1 2000
    Thread-2 2000
    

    上述示例说明:线程与线程之间相互隔离,且线程安全。ThreadLocal作用域为当前线程

     
     
    三、源码分析
    ThreadLocal为什么可以做到每一个线程都有一份变量的副本,其实原理很简单
    ThreadLocal原理同HashMap原理是一样的,核心为ThreadLocalMap,只不过是Map的key为当前线程,通过Thread.currentThread()获取。只要是在同一个线程内,只要你执行set方法,这个key永远保持不变,即每一个线程对应一个value。
     
     
    我们先通过ThreadLocal的set方法来分析
    // set方法一般都是set(key, value),因为ThreadLocalMap使用了当前线程作为key,所以省略了,get()方法也一样。
    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;		// 这个变量在Thread类中,ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    首次在调用 set方法时,会执行createMap,在createMap方法中又会创建一个ThreadLocalMap对象,我们再来看一下ThreadLocalMap这个构造方法

    构造方法
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    

    通过构造方法,我们可以看出ThreadLocal底层使用的就是HashMap结构

    ThreadLocalMap继承了WeakReference
    static class ThreadLocalMap {
    	static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }        
        }
        
        private static final int INITIAL_CAPACITY = 16;
        private Entry[] table;
        
    }
    

      

    ThreadLocal为什么要使用弱引用?
    首先我们看一个WeakHashMap的示例,WeakHashMap也是它的 Entry类继承了WeakReference
     
    public static void main(String[] args) throws InterruptedException {
        WeakHashMap<String, String> weakMap = new WeakHashMap<String, String>();
        String key = new String("1");
        String key2 = "2";
        weakMap.put(key, "test");
        weakMap.put(key2, "test2");
        System.out.println(weakMap);			// {1=test, 2=test2}
    
        key = null;		
        key2 = null;	// 不会被回收
        System.gc();
        System.out.println(weakMap);			// {2=test2}
    }
    

    我们可以看出WeakHashMap针对key作了回收,而在整个map中并没有真正的回收此对象。在ThreadLocal中,它使用当前线程作为key的,如果线程生命周期结束后,即这个key以及对就的value都应该被GC掉

     
     
    内存泄露问题
    虽然上述的弱引用解决了key,也就是线程的ThreadLocal能及时被回收,但是value却依然存在内存泄漏的问题。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。map里面的value却没有被回收.而这块value永远不会被访问到了。所以存在着内存泄露,因为存在一条从current thread连接过来的强引用。只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
     
     
     
    ThreadLocal和Synchonized的比较:
    • ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
    • ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比Synchronized要简单得多。
    • ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与Synchronized有本质的区 别。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本, 使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
    • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
    当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
     
  • 相关阅读:
    扩展的friend语法
    常量表达式
    字符串过滤,排序输出数字的问题
    decltype类型声明- 现代C++新特性总结
    auto类型-现代C++新特性
    指针相关总结2
    指针相关总结1
    发现XMind一个超级牛逼的功能
    空类指针为什么可以调用类的成员函数 以及 A(){}和A();
    SSAS父子层次结构的增强-UnaryOperatorColumn属性
  • 原文地址:https://www.cnblogs.com/caoxb/p/13140146.html
Copyright © 2020-2023  润新知