ThreadLocal初识


背景

这两天稍微有点空,在追溯之前的android 7.0之前的手机用View.post 出现不执行的问题时,结识了ThreadLocal,且问题的原因也是系统内部使用ThreadLocal造成的。因此记录并分享之。
关于view.post不执行的坑点

ThreadLocal的作用

ThreadLocal的作用主要是做数据隔离,填充的数据(对象)只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,一个线程防止自己的变量被其它线程篡改。1
A线程存了一个对象objectA,此时B线程通过同一个ThreadLocal对象去取的话是取不到objectA的。

通俗的讲:
A、B钱包都没钱了,A从银行取了1000 人民币,装入了自己的钱包,B去商店买1000的商品,此时B从自己钱包里面拿钱时,钱包是空的。
AB好比是线程,存的数据就是1000人民币,消费的时候只能从自己钱包拿出来,只是A和B存到钱包的过程可以通过ThreadLocal来完成的。

ThreadLocal特性

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是2

  1. Synchronized是通过线程等待,牺牲时间来解决访问冲突
  2. ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

ThreadLocal实现

ThreadLocal 对外一共提供了get、set和remove的函数,这里请注意set和get不是一般bean的get和set。而且巧妙的使用了调用栈关系,并取了当前调用的线程来做一些列的处理,所以与线程密切相关。调用线程指的是执行get、set、和remove的线程。

1. T get()

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

2. set(T value)

/**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

3. remove()

 /**
     * Removes the entry for this variable in the current thread. If this call
     * is followed by a {@link #get()} before a {@link #set},
     * {@code #get()} will call {@link #initialValue()} and create a new
     * entry with the resulting value.
     *
     * @since 1.5
     */
public void remove() {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            values.remove(this);
        }
 }
Values values(Thread current) {
     return current.localValues;
}

从get/set/remove实现中能看出,都是取当前线程,再从当前线程中拿出values,从values取出或put。而这个values是属于Thread的莫不是ThreadLocal的,这点要注意一下。value内部包含了数组,那么程序存取的数据对象就是放到这个数组中的,并且是以hash进行映射,和HashMap的实现不一样,思想几乎一样。

验证

接下来需要代码实际演示一下,不然死不了心。那么问题来了,从get和set来看都只有一个value参数,假如要set多个怎么办,而且set又是以ThreadLocal对象作为key去“存储”的。不要慌,既然是以ThreadLocal对象作为key,那就多创建几个ThreadLocal对象,多个对象被一个线程调用,那么多个数据就被存到了同一个线程的存储区(table)中。

一个对象只存一个数据

static void testThreadLoacal() {
        final ThreadLocal<String> threadLocal = new ThreadLocal<>();
        String str = "Test ThreadLocal";
        threadLocal.set(str);
        System.out.println("main thread set value:" + str);
        new Thread() {
            @Override
            public void run() {
                String str = threadLocal.get();
                System.out.println("sub thread get value:" + str);

            }
        }.start();
        str = threadLocal.get();
        System.out.println("main thread get value:" + str);
    }

输出结果:

main thread set value:Test ThreadLocal
main thread get value:Test ThreadLocal
sub thread get value:null

一个线程set的值,其他线程取不到,只有相同线程才能取到。多个线程同时用一个ThreadLocal对象,其数据是还是属于各自线程。

多少个对象就能存多少个数据

由于是泛型,所以多个ThreadLocal不但可以存多个同类型的对象,甚至可以存多个不同类型的 对象。

static void testThreadLoacals() {
        final ThreadLocal<String> threadLocal = new ThreadLocal<>();
        final ThreadLocal<A> threadLocal1 = new ThreadLocal<>();
        String str = "main Thread";
        threadLocal.set(str);
        System.out.println("threadLocal main thread set str value:" + str);
        A a = new A();
        threadLocal1.set(a);
        System.out.println("threadLocal1 main thread set A class value:" + a);
        new Thread() {
            @Override
            public void run() {
                String str = threadLocal.get();
                System.out.println("sub thread get value:" + str);
                str = "sub thread";
                threadLocal.set(str);
                System.out.println("sub thread set value:" + str);
                System.out.println("sub thread set value:" + threadLocal.get());
            }
        }.start();
        str = threadLocal.get();
        System.out.println("main thread 1 get value:" + str);
        System.out.println("main thread 1 get A class value:" + threadLocal1.get());
        try {

            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("main thread 2 get value:" + threadLocal.get());
    }

threadLocal set 一个String
threadLocal1 set的是A类的对象

输出结果:

threadLocal main thread set str value:main Thread
threadLocal1 main thread set A class value:com.eagle.app.MainJava$A@12a3a380
main thread 1 get value:main Thread
main thread 1 get A class value:com.eagle.app.MainJava$A@12a3a380
sub thread get value:null
sub thread set value:sub thread
sub thread set value:sub thread
main thread 2 get value:main Thread

多个线程同时用多个ThreadLocal对象,其数据是还是属于各自线程。


  1. Java中ThreadLocal的实际用途是啥 ↩︎

  2. ingxin ThreadLocal ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值