美文网首页
ThreadLocal

ThreadLocal

作者: 土肥圆的诺诺 | 来源:发表于2019-01-11 17:11 被阅读31次
    每天学习一点点

    ThreadLocal在android中属于一个常用的类,比如的Android最重要的Handler消息机制里面的Looper存储也是采用ThreadLocal,开源框架EventBus存储当前线程下的发送事件队列状态也是采用ThreadLocal。

    ThreadLocal到底能做什么呢?原理又是什么呢?

    点进ThreadLocal的类文件看一下,类上写的注释。

    This class provides thread-local variables.  These variables differ from
     * their normal counterparts in that each thread that accesses one (via its
     * {@code get} or {@code set} method) has its own, independently initialized
     * copy of the variable.  {@code ThreadLocal} instances are typically private
     * static fields in classes that wish to associate state with a thread (e.g.,
     * a user ID or Transaction ID).
     *
     * <p>For example, the class below generates unique identifiers local to each
     * thread.
     * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
     * and remains unchanged on subsequent calls.
     * <pre>
     * import java.util.concurrent.atomic.AtomicInteger;
     *
     * public class ThreadId {
     *     // Atomic integer containing the next thread ID to be assigned
     *     private static final AtomicInteger nextId = new AtomicInteger(0);
     *
     *     // Thread local variable containing each thread's ID
     *     private static final ThreadLocal&lt;Integer&gt; threadId =
     *         new ThreadLocal&lt;Integer&gt;() {
     *             &#64;Override protected Integer initialValue() {
     *                 return nextId.getAndIncrement();
     *         }
     *     };
     *
     *     // Returns the current thread's unique ID, assigning it if necessary
     *     public static int get() {
     *         return threadId.get();
     *     }
     * }
     * </pre>
     * <p>Each thread holds an implicit reference to its copy of a thread-local
     * variable as long as the thread is alive and the {@code ThreadLocal}
     * instance is accessible; after a thread goes away, all of its copies of
     * thread-local instances are subject to garbage collection (unless other
     * references to these copies exist).
    

    大体意思就是ThreadLocal 实现一个线程本地的存储。每个线程都有自己的局部变量。所有线程都共享一个ThreadLocal对象,但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。

    我们看下ThreadLocal的方法有哪些?
    • void set(Object value)设置当前线程的线程局部变量的值。
    • public Object get()该方法返回当前线程所对应的线程局部变量。
    • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
    • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
    我们使用一下ThreadLocal

    创建一个ThreadLocal,存入一段文本,看在两个线程取出值是多少。

     local = new ThreadLocal<>();
            local.set("存入测试");
            Thread t1 = new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
                    Log.e(TAG, "t1 get(): " + local.get());
                    local.set("t1");
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "t2 get() " + local.get());
                }
            });
    
            t1.start();
            t2.start();
         Log.e(TAG, "main get(): "+local.get() );
    
    结果

    我们发现在t1 t2中取出值都是null,那么我们在t1 t2中存储再取出验证下

     local = new ThreadLocal<>();
            local.set("存入测试");
            Thread t1 = new Thread(new Runnable() {
    
    
                @Override
                public void run() {
                    local.set("t1");
                    Log.e(TAG, "t1 get(): " + local.get());
    
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    local.set("t2");
                    Log.e(TAG, "t2 get() " + local.get());
                }
            });
            t1.start();
            t2.start();
            Log.e(TAG, "main get(): "+local.get() );
    
    image.png

    获取大家好奇,怎么就在拿不到local的值呢,我们上面说到ThreadLocal是每个线程可以访问自己内部的副本变量。

    我们看下ThreadLocal是怎么实现这种神奇机制的的?

    先看下set方法

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

    我们不去关注细节,大家有时间可以仔细看下源码,我们获取了一个map,进行了判null处理,将值存进去。如果是null就去创建map并将值赋进去。可以理解为key就是当前的线程。
    再看一下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();
        }
    
    

    根据线程,取出一个map,并根据类型取出一个Entry,根据泛型强转,并返回,如果没有的话,我们看到一个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;
        }
    
    protected T initialValue() {
            return null;
        }
    

    可以看到如果没查到值initialValue,会默认返回一个null。
    其实ThreadLocalMap的源码大家可以看一下,因为代码量较多,我就不粘出来了。我们刚看到的Entry其实是一个WeakReference。

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

    最后看下remove()方法

             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    

    其实也就是根据当前线程判断,删除this,这里的this指向的就是ThreadLocal自己。
    其实还有很多细节,大家可以研究下源码的时候看一下,想一下
    这篇文章写的不错,大家可以看看。
    https://www.cnblogs.com/xzwblog/p/7227509.html

    相关文章

      网友评论

          本文标题:ThreadLocal

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