多线程高并发开发Android多线程程序员

多线程知识梳理(9) - ThreadLocal

2018-01-17  本文已影响129人  泽毛

一、基本概念

1.1 ThreadLocal 的用途

首先,我们来看一下JDK源码中对于ThreadLocal的解释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one has its own, independently initialized copy of the variable. ThreadLocal instances are typically privatestatic fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻译过来就是:

ThreadLocal用来提供线程内的局部变量。这些变量在多线程环境下访问时能够保证各个线程里的变量相对独立于其它线程内的变量,ThreadLocal实例通常来说都是private static类型的。

因此,ThreadLocal适用于满足下面条件的场景:

1.2 ThreadLocal 的使用

ThreadLocalAPI很简单,它包含以下四个签名:

我们用下面的一小段例子,来熟悉一下ThreadLocal的使用。

class ThreadLocalSamples {

    private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>() {

        @Override
        protected Integer initialValue() {
            return 5;
        }

    };

    static void startSample() {
        for (int i = 0; i < 3; i++) {
            new SampleThread("thread_" + i).start();
        }
    }

    private static class SampleThread extends Thread {

        private String mThreadName;

        SampleThread(String threadName) {
            mThreadName = threadName;
        }

        @Override
        public void run() {
            for (int j = 0; j < 5; j++) {
                try {
                    long sleep = (long) (Math.random() * 50);
                    Thread.sleep(sleep);
                    int result = sThreadLocal.get();
                    sThreadLocal.set(++result);
                    Log.d("ThreadLocalSamples", "ThreadName=" + mThreadName + ",result=" + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

运行结果
从打印的结果可以看到,虽然这3个线程访问是同一个ThreadLocal实例,但是它们通过ThreadLocalget/set方法读写的并不是同一个实例,所以保证了在多线程环境下的独立性。

二、源码

2.1 源码实现

为了加深对于ThreadLocal的理解,我们来分析一下它的内部实现。ThreadLocal设计的核心思想就是:每一个Thread维护一个ThreadLocalMapThreadLocalMapkeyThreadLocal,而value就是真正要存储的Object。这种方案设计的优点是:

我们先来看一下setget的主要流程:

    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();
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    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);
    }

写入的流程为:

读取的流程为:

总结下来就是:ThreadLocal将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦

它之所以可保证 多线程环境下的相互独立,原因在于:每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,线程可以正确的访问到自己的对象。

当然,这种 独立性必须要基于一个前提通过set方法存储的对象并不是多个线程共享的。如果是共享的,那么多个线程get出来的是同一个是实例,仍然会存在多线程问题。

2.2 ThreadLocalMap

ThreadLocalMapThreadLocal中的一个内部类,与HashMap类似,它也会遇到Hash冲突的问题,HashMap采用了 链地址法 解决冲突,而ThreadLocalMap则采用 开放寻址法 解决冲突。

关于ThreadLocalMap还有一个疑问,就是它有可能会出现内存泄漏,原因是:ThreadLocalMapkey值保存的是ThreadLocal的弱引用,假如ThreadLocal被回收,那么就会无法通过Key找到Object,假如线程一直没有结束,那么这些Object就永远不会被回收。

ThreadLocalMap内部对于这种情况做了优化,就是在getEntryset方法查找存储位置的时候,如果发现了keynull的槽,那么会将这些槽中对应的Object引用置为null。这并不能解决所有问题,对于使用者来说,可以做额外的两项优化操作:

参考文献

(1) 正确理解 ThreadLocal
(2) 深入剖析 ThreadLocal 实现原理以及内存泄漏问题
(3) ThreadLocal 和 synchronized 的区别

上一篇 下一篇

猜你喜欢

热点阅读