ThreadLocal是什么呢

前言:上次文章说到在面试的过程中很多候选者对Handler如何达到切换线程的效果不是很清晰,所以那篇文章对此做了解释。但是在文章中解释Looper的时候牵扯到了ThreadLocal这个东西我却没有做仔细的说明只是一笔带过了,所以今天我把ThreadLocal的文章给补上啦。

ThreadLocal是什么呢,它是怎么供我们使用的呢?

先简单说:ThreadLocal就是能够存储数据,它的作用域是线程。

下面我将先演示一下ThreadLocal是如何在项目中使用的,并且给大家看一下对它使用的结果,让猿友们能够比较清晰的理解ThreadLocal作用域为线程这个特点,然后在后面会用源码层次的原理来对这个结果作出解析。

请看下面的示范过程:

我在Activity里创建一个静态的ThreadLocal类,并且创建了三个线程,这个三个线程通过点击按钮来触发执行。

这里要注意的是我已经赋值ThreadLocal对象为main字符串了。

public class SixActivity extends AppCompatActivity {

    public static ThreadLocal<String> stringThreadLocal;
    private Button btn_one,btn_two,btn_three;
    private Thread thread_one,thread_two,thread_three;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_six);

        btn_one = (Button)findViewById(R.id.btn_one);
        btn_two = (Button)findViewById(R.id.btn_two);
        btn_three = (Button)findViewById(R.id.btn_three);

        thread_one = new SixThread("one");
        thread_two = new SixThread("two");
        thread_three = new SixThread("three");

        stringThreadLocal = new ThreadLocal<>();
        stringThreadLocal.set("main");

        btn_one.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread_one.start();
            }
        });

        btn_two.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                thread_two.start();
            }
        });

        btn_three.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                thread_three.start();
            }
        });
    }
}

在下面的这个自定义的SixThread线程执行时,首先会打印一下从ThreadLocal对象获取的数据,然后对其赋值,再获取其值并打印出来。

public class SixThread extends Thread {

    SixThread(String name){
        super(name);
    }
    @Override
    public void run() {

        if (SixActivity.stringThreadLocal.get() != null) {
            Log.v(getName(),SixActivity.stringThreadLocal.get());
        }else {
            Log.v(getName(),"为空");
        }

        SixActivity.stringThreadLocal.set(getName());

        if (SixActivity.stringThreadLocal.get() != null) {
            Log.v(getName(),SixActivity.stringThreadLocal.get());
        }else {
            Log.v(getName(),"为空");
        }
    }
   }

到此,我们首先将三个按钮依次点一遍,这三个子线程都打印为开始时ThreadLocal为空,可是明明之前在主线程赋值了,但是后面在自己的线程赋值后再打印出来时,获取的值是没有问题的。

下面是打印的运行结果:

03-26 11:41:41.377 11900-12028/com.example.zth.seven V/one: 为空
03-26 11:41:41.377 11900-12028/com.example.zth.seven V/one: one
03-26 11:41:44.157 11900-12042/com.example.zth.seven V/two: 为空
03-26 11:41:44.157 11900-12042/com.example.zth.seven V/two: two
03-26 11:41:46.270 11900-12044/com.example.zth.seven V/three: 为空
03-26 11:41:46.270 11900-12044/com.example.zth.seven V/three: three

这时候如果我们再按一遍这三个按钮呢,我们会发现每个线程还记得上一次线程记录的值,这下子大家对ThreadLocal对象是用来存储数据并作用域为线程的特性会有了清晰的认识了吧。

下面是这次点击的运行结果:

03-26 11:41:48.140 11900-12050/com.example.zth.seven V/one: one
03-26 11:41:48.140 11900-12050/com.example.zth.seven V/one: one
03-26 11:41:54.241 11900-12070/com.example.zth.seven V/two: two
03-26 11:41:54.241 11900-12070/com.example.zth.seven V/two: two
03-26 11:41:54.882 11900-12071/com.example.zth.seven V/three: three
03-26 11:41:54.883 11900-12071/com.example.zth.seven V/three: three

演示过程到此结束了,下面的篇章将会从源码的角度来解析ThreadLocal的这些效果和特性的原因何在。

在下面的源码中,我们可以看到这个set函数的源码,会先获取当前线程,然后获取与当前线程相对的ThreadLocalMap对象,如果有就像map数据结构一样赋值键值对,如果没有就创建ThreadLocalMap并赋值键值对。在这里可以看到Thread类的threadLocals属性被使用来存储ThreadLocalMap值。

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

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

然后我们再来看看下面的get函数的源码,会先获取执行get函数的线程对象,然后getMap是返回线程的threadLocals值,然后判断map对象是否为空,如果为空就返回默认值,否则从这个ThreadLocalMap对象里取出entry对象,返回entryvalue值

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

最后,总的来说就是通过将每个Thread对象的threadLocals里的ThreadLocalMap对象的Enrty对象来存储数据,说白了就是将数据存储在每个Thread对象里,这样就可以做到作用范围是Thread对象了,就是这么的简单。

好了,ThreadLocal就到此为止了,希望可以对你有所帮助,see you


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值