美文网首页
ThreadLocal完全解析

ThreadLocal完全解析

作者: anrikuwen | 来源:发表于2019-05-12 16:22 被阅读0次

熟悉Android消息机制的话,对ThreadLocal这个类应该都不陌生。Android消息机制中的Looper就是通过ThrealLocal来实现为每个线程建立一个独立Looper的。

简单的Demo

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        // 给主线程设置ThreadLocal值
        threadLocal.set("I am in main thread");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 给子线程设置ThreadLocal值
                threadLocal.set("I am in sub thread");
                // 在子线程获取ThreadLocal值
                System.out.println(Thread.currentThread() + ":" + threadLocal.get());
            }
        });
        thread.start();
        // 等待子线程执行完
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在主线程获取ThreadLocal值
        System.out.println(Thread.currentThread() + ":" + threadLocal.get());
    }
    
}

通过上面程序可以发现ThreadLocal在不同的线程设置和获取值是不会相互影响的。

ThreadLocal是如何实现不同线程独立存储的

其实每个线程都一个叫做threadLocalsThreaLocal.ThreadLocalMap类型成员属性。

深入源码可以发现ThreadLocalMap就是一个简易版的Hash表。这个Hash表keyWeakReference<ThreadLocal<?>>valueObject熟悉范性的同学都应该知道ThreadLocal<任意类型>都可以赋值给ThreadLocal<?>类型,相当于实现了范性的多态

ThreadLocal调用set方法的时候,最终都会以当前ThreadLocal的弱引用为key对应的set的值为value存入当前调用线程的threadLocals成员属性中去。

因为如果在不同线程同一ThreadLocal进行set方法调用,在set方法内获取的是不同Thread的threadLocals成员属性,因此达到了同一个ThreadLocal对象的在不同线程中进行值独立存储的要求。

下面是Thread的部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

最核心的类---ThreadLocalMap

前面已经说了,这就是一个简易版的在Hash表这里key为ThreadLocal的弱引用因此ThreadLocal是不会导致内存泄漏的。只要某个ThreadLocal的强引用没有了,GC时就ThreadLocalMap对应的ThreadLocal的弱引用也会被回收。

这个过程会产生不新鲜值也就是ThreadLocalMap中某个位置的弱引用的值为null,但其对应的value不为null。

不新鲜值在每次set方法调用的时候一定会进行相应的检测清除,get方法只有在遇到不新鲜值的时候才会进行相应的清除。对应的算法就不进行详解了。

这个简易版的Hash表以16作为初始容量,然后扩容因子2 / 3。以2倍进行扩容。因此其容量始终是2的幂次方。这样的好处和HashMap一样的可以通过与运算轻松的获取存储的值该放在Hash表的哪个位置。然后如果遇到Hash冲突,这里使用的是索引加一法进行冲突的解决。

下面是ThreadLocal.ThreadLocalMap的部分源码:

public class ThreadLocal<T> {
    ...
    static class ThreadLocalMap {
        ...
        private Entry[] table;
        
        static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
      }
    }
        ...
    }
    ...
}

ThreadLocal的实现

public class ThreadLocal<T> {
...
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    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();
    }
  

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
...
}

通过上面部分源码应该很容易发现ThreadLocal就是借助于Thread的threadLocals整个Hash表进行不同线程值的独立存储的。

InheritableThreadLocal---可以继承的ThreadLocal

在Thread的源码中应该可以发现其实Thread有两个ThreadLocal.ThreadLocalMap类。

下面是部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
    }
...

}

threadLocals就是使用普通的ThreadLocal类用到的;inheritableThreadLocals这个成员属性主要就是用于继承创建当前Thread的父Thread中的inheritableThreadLocals。

init方法是继承的具体实现。

下面是InheritableThreadLocal部分源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal就是ThreadLocal的子类,很简单就是使用线程的threadLocals改成了使用inheritableThreadLocals进行进行存储。

总结

总的来说,ThreadLocal.ThreadLocalMap就是一个通过索引加一法解决冲突的Hash表。

然后每个Thread中有两个ThreadLocal.ThreadLocalMap类型的成员属性:

  • threadLocals用于对ThreadLocal进行存储的。

  • inheritableThreadLocals用于对InheritableThreadLocal进行存储的。其中inheritableThreadLocals会在Thread的init方法调用的时候继承父Thread的inheritableThreadLocals。

相关文章

  • ThreadLocal完全解析

    熟悉Android消息机制的话,对ThreadLocal这个类应该都不陌生。Android消息机制中的Looper...

  • ThreadLocal源码完全解析

    最近有小伙伴问我ThreadLocal的实现是怎样的,作为一个Java小能手,我就大(装)致(逼)的说了下。他听后...

  • 线程中的ThreadLocal

    ThreadLocal解析

  • JDK ThreadLocal解析

    Java ThreadLocal解析 ThreadLocal 线程本地变量, 线程私有, 在 Thread 类中用...

  • ThreadLocal 深度解析

    一.对ThreadLocal的理解二.深入解析ThreadLocal类三.ThreadLocal的应用场景 对Th...

  • ThreadLocal解析

    前言 刚看过EventBus和AndroidEventBus的源码, 发现里面都有用到ThreadLocal, 那...

  • ThreadLocal解析

    前言 在各大公司招聘笔试和面试题题中,都遇到了很多ThreadLocal的问题,最近博主在面试的时候也被两次问到过...

  • ThreadLocal解析

    一、简介 首先我们需要知道Thread.currentThread()获取当前线程对象,同一个线程每次获取的都是同...

  • ThreadLocal解析

    前言 我们都知道ThreadLocal用于为每个线程存储自己的变量值,起到线程间隔离的作用,那么它到底是怎么运行的...

  • ThreadLocal解析

    ThreadLocal,顾名思义,肯定是与Thread类有关系的,所以先从Thread类入手。 Thread和Th...

网友评论

      本文标题:ThreadLocal完全解析

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