ThreadLocal解析

0、目录

1、引入

2、介绍

3、实例

4、原理与源码

5、总结

1、引入

在Handler使用中,每个线程都有一个对应的Looper,主线程通过 Looper.getMainLooper()获得与主线程对应的Looper,子线程中通过 Looper.myLooper()获取与该子线程对应的Looper。

//Looper.myLooper()的源码:
   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        //sThreadLocal定义:
        //static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        return sThreadLocal.get();
    }

通过源码可以看出,当前线程的Looper是存放在ThreadLocal类型的sThreadLocal内,当需要该线程的Looper时,直接读取sThreadLocal内的数据即可。

那么ThreadLocal到底是怎么使用的,原理又是什么,下面就介绍一下ThreadLocal。

2、介绍

ThreadLocal通过字面直接翻译,就是线程本地 ,也就是线程本地变量。

ThreadLocal的作用域仅仅是本线程内,即ThreadLocal为每个线程提供了一个特定的变量,以保存该线程所独享的数据,所以其提供了一种隔离线程,防止线程间数据共享的方法。

所以ThreadLocal的使用场景,一般当某些数据是以线程为作用域并且不同线程都具有不同的数据副本时,可以考虑使用ThreadLocal,比如每个线程都有各自线程对应的Looper,所以就使用了ThreadLocal。

3、实例

单纯的介绍比较抽象,用一个实例来感受一下。

public class MainActivity extends AppCompatActivity {
        Button mBtn;
        @Override
    	protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mBtn = (Button) findViewById(R.id.mBtn);

            mBtn.setOnClickListener(new View.OnClickListener() {
           		 @Override
                public void onClick(View v) { 
                    new Thread(new MyRunnable(), "Thread1").start();
                    new Thread(new MyRunnable(), "Thread2").start();
                }
           });
        }

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            ThreadLocal mThreadLocal = new ThreadLocal<String>();
            ThreadLocal mThreadLocal2 = new ThreadLocal<String>();

            mThreadLocal.set(Thread.currentThread().getName() + "的ThreadLocal");
            mThreadLocal2.set(Thread.currentThread().getName() + "的ThreadLocal2");
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                e.getMessage();
            }

            System.out.println(Thread.currentThread().getName() + ":" + mThreadLocal.get());
            System.out.println(Thread.currentThread().getName() + ":" + mThreadLocal2.get());
        }
    }
}

调出Android Studio的logcat,在过滤器中输入System.out,就可以看到输出信息,具体如下:

通过实例可以看到,这两个线程分别设置了ThreadLocal 的值并分别取出,线程之间是相互独立的,并没有任何干扰。所以从这里也能体会到,ThreadLocal是线程局部变量,作用域仅仅是本线程。

另外,同一个线程下,也可以设置多个ThreadLocal 变量。

4、原理与源码

在使用ThreadLocal时,首先创建ThreadLocal对象实例,然后调用了该对象的set()和get()方法。

set()方法:

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value. 
     */
 public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、根据当前线程,获取ThreadLocalMap 对象  
        ThreadLocalMap map = getMap(t);-->>分析1
        if (map != null)
            //3、如果当前线程ThreadLocalMap 对象存在,则替换掉原来的值
            //通过这里能够看到,这个ThreadLocalMap对象的键是当前的 ThreadLocal 实例
            map.set(this, value);
        else
            //4、如果当前线程ThreadLocalMap 对象不存在,
            //创建一个新的ThreadLocalMap 对象,并赋值
            createMap(t, value);//-->>分析3
    }

分析1:
   /**
     * Get the map associated with a ThreadLocal.
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //-->>分析2
    }

分析2:
    //threadLocals在Thread类中的定义如下
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

//分析3
    //Create the map associated with a ThreadLocal
    void createMap(Thread t, T firstValue) {
        //创建一个 ThreadLocalMap对象,键就是当前ThreadLocal实例,值是firstValue
        //并把新创建的 ThreadLocalMap对象赋值给当前线程t的变量threadLocals 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

get()方法:

   /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the initialValue method.
     */
    public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取与当前线程对应的ThreadLocalMap 对象
        ThreadLocalMap map = getMap(t);
        //3、判断ThreadLocalMap 对象是否存在,如果存在直接返回值  
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        //4、如果不存在,初始化。
        return setInitialValue();  //-->>分析1
    }

分析1:
    private T setInitialValue() {
        //1、初始化值
        T value = initialValue();//-->>分析2
        //2、获取当前线程
        Thread t = Thread.currentThread();
        //根据当前线程获取ThreadLocalMap 对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //如果ThreadLocalMap 对象存在,直接替换原来的值
            map.set(this, value);
        else
            //如果不存在,则创建新的ThreadLocalMap 对象,并赋值
            createMap(t, value);
        return value;
    }

分析2:
/* This implementation simply returns null; if the programmer desires thread-local variables to have         
 * an initial value other than null, ThreadLocal must be subclassed, and this method overridden.     
 * Typically, an anonymous inner class will be used.
 */
     protected T initialValue() {
        return null;
    }

    //另外值得一提的是,initialValue()方法在创建ThreadLocal对象时是可以复写的,比如
    ThreadLocal mThreadLocal = new ThreadLocal<String>(){ 
                @Override
                protected String initialValue() {
                    //在使用的时候,只要把super.initialValue()改为自己的初始值即可
                    //比如,我想设置初始值为”Hello World!“
                    //则直接修改为 return "Hello World!"; 即可
                    return super.initialValue();
                }
            };

4、总结

通过分析,再整理一下。

ThreadLocal的原理,就是每个线程都对应一个与该线程相关的ThreadLocalMap ,而这个Map是键值对,键Key就是当前的ThreadLocal对象实例,值就是自己设置的或者初始值(也就是ThreadLocal变量的值),这个值可以自己设置或获取,分别调用ThreadLocal.set(value)和ThreadLocal.get()方法。

所以在不同的线程中调用同样的ThreadLocal.get()方法,会从自己当前的线程中获取对应的ThreadLocalMap对象,然后再根据当前线程ThreadLocal对象实例作为键,查询对应的值。不同的线程得到不同的ThreadLocalMap对象,虽然ThreadLocal对象实例键相同,依然会得到不同的值。

这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本,并且彼此之间还能不被干扰。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值