ThreadLocal详解
阅读原文时间:2021年04月20日阅读:1

概念

ThreadLocal是一个线程内部的数据存储类,通过它我们可以在指定的线程中存储数据,但是在数据存储以后,我们也只能在指定的线程中获取到存储的数据。

应用场景:

  1. 当某些数据需要以线程为作用域的时候并且不同线程具有不同的数据副本,就可以考虑使用ThreadLocal,比如对于Handler来说,它需要获取当前线程的Looper,但是Looper的作用域是就是线程并且不同的线程具有不同的Looper,这时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。

    public final class Looper {
    //ThreadLocal 存储value类型为Looper
    static final ThreadLocal sThreadLocal = new ThreadLocal();

        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    public static void prepare() {
        prepare(true);
    }
    
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //存储Looper到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    //获取Looper对象
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    } }`</code></pre></li>

从Looper源码中,我们可以看出Looper在prepare()初始化过程中存储到ThreadLocal,如果我们想获取Looper对象,直接在对应线程中通过Looper.myLooper()方法获取;
 2.监听器的传递,有时候一个线程中的任务过于复杂,代码上表现为函数调用栈比较深获取代码入口多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,数据库连接管理,数据库事务。然后常见的方法就是:
  1.监听器通过参数的形式在函数调用栈中传递。调用栈过深会导致程序结构及其糟糕
  2.将监听器作为静态变量供线程访问,这种方式不具备扩展性,如果有两个线程同时执行,就必须提供两个静态的监听器对象,那如果有10个线程在并发执行?提供10个静态的监听器对象?

但是如果使用ThreadLocal,就可以让监听器作为线程内的全局对象,在线程内部只需要通过get方法就可以获取到监听器。例如下载操作中,我们监听文件的下载进度,我们需要在下载完以后进行压缩,那么在CompressManager中我们直接就可以获取OnDownloadListener对象,而不需要通过参数传递。

public class DownloadTask {
    static final ThreadLocal<OnDownloadListener> mThreadLocal = new ThreadLocal<>();
    OnDownloadListener mOnDownloadListener;
    public DownloadTask(OnDownloadListener onDownloadListener) {
        mOnDownloadListener = onDownloadListener;
        mThreadLocal.set(onDownloadListener);
    }
    public static OnDownloadListener getOnDownloadListener() {
        return mThreadLocal.get();
    }
    public void download() {
        //start download
        getOnDownloadListener().startDownload();
        //..... 

        //pause download
        getOnDownloadListener().pauseDownload();
        //cancel download
        getOnDownloadListener().cancelDownload();
    }
} 
   class CompressManager{
        void compress(){
            //compressing...
            //compress complete
            DownloadTask.getOnDownloadListener().downloadComplete();
        }
    }

小试牛刀

看了上面的应用场景,我们来使用下ThreadLocal,看看是否那么神奇

   ThreadLocal mThreadLocal = new ThreadLocal<>();
        mThreadLocal.set(true);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mThreadLocal.set(false);
                Log.i("test","subThread1:"+mThreadLocal.get());
            }
        },"subThread1").start();
        new Thread("subThread2"){
            @Override
            public void run() {
                super.run();
                Log.i("test","subThread2:"+mThreadLocal.get());
            }
        }.start();
        Log.i("test","mainThread:"+mThreadLocal.get());

上面的代码中,我们先在主线程中设置为true,subThread1中设置为false,subThread2中不设置值,结果在不同的线程中访问的是同一个ThreadLocal对象,但是他们通过ThreadLocal获取的值却是不一样的。

内部实现

那么我们来看下ThreadLocal内部是怎么实现的?
其实只要弄清楚ThreadLocal中的get和set方法就可以明白他的工作原理。

public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
 public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }
  Values values(Thread current) {
        return current.localValues;
    }

从上面两个方法可以看出来,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读写操作都仅限于线程内部。