关于ThreadLocal的理解

先上一个使用ThreadLocal实例的demo,ThreadLocalDemo 实例包含一个ThreadLocal实例。从网上各种信息看到ThreadLocal是线程私有变量。保持了每个变量的副本,其实ThreadLocal不能用于解决多线程共享变量问题。
ThreadLocal 中只是保存该线程自己创建的局部变量的副本。如果是多线程共享的变量还是会发生不能同步该的后果。下面这个例子就是启动两个线程,通过threadLocal实例的set方法将person实例加入到线程本地变量ThreadLocal.Values localValues;中。但是localValues这个变量底层实现十基于数组的一个map结构。对于引用变量缓存引用。所以在这个demo中两个线程的localValues变量都指向了同一个person实例。也就不是线程私有的变量。要达到线程私有的话只有在线程中通过创建的变量通过ThreadLocal的set方法插入的元素才是线程私有的变量。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多个线程使用同一个Person对象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(888);
                perLocal.setName("Admin");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                System.out.println(Thread.currentThread().getName() + " :"
                        + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th1.join();
        Thread.sleep(100);
        th2.start();

        th2.join();
        // Person对象已经被线程给修改了!
        System.out.println("Person对象的值为:" + per);
    }


}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId( int id ) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

输出:
可以看到虽然将person实例加入到两个线程本地变量ThreadLocal.Values localValues; 但是都是指向同一个实例person。所以从输出结果可以看到在th1线程更改了person后,在th2线程也可以获取到最新的结果。

thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=888, name=Admin]
thread-th2 :Person [id=888, name=Admin]
Person对象的值为:Person [id=888, name=Admin]

下面给出在线程里面new一个实例然后通过ThreadLocal类的set方法插入到当前线程的ThreadLocal.Values localValues;变量中 ,最后对当前线程的localValues变量中的本地变量通过ThreadLocal类的get()方法获取到当前线程threadLocal实例为key对于的值。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
//定义一个ThreadLocal类实例,这个是插入线程本地变量的接口类,
//有get/set方法;ThreadLocal类其实只是封装了插入线程本地变量的操作接口,
//每个线程的线程本地变量ThreadLocal.Values localValues就是一个map存储
//结构,以ThreadLocal类实例为key,存储的数据为值。
//如果需要获取到这个本地变量,只需要在线程内部通过ThreadLocal类实例的get()方法就可以获取到与ThreadLocal类实例对于的值。

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多个线程使用同一个Person对象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(111, "Sone"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(9999);
                perLocal.setName("jim");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(112, "vincent"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(8);
                perLocal.setName("jack");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        // Person对象已经被线程给修改了!
        System.out.println("Person对象的值为:" + per);
    }

    public void run() {

    }
}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId( int id ) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName( String name ) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

输出结果:
从输出结果可以看到我在各自线程创建一个person实例,然后插入到线程本地变量中(ThreadLocal.Values localValues; 是一个map结构的数据结构),每个线程有具有一个这样的实例;插入使用的是相同的ThreadLocal类实例为key,但是缓存的是不同person变量。

thread-th2 before:Person [id=112, name=vincent]
thread-th2 after:Person [id=8, name=jack]
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=9999, name=jim]
Person对象的值为:Person [id=111, name=Sone]

最后说下看ThreadLocal类后对他的理解,首先ThreadLocal不是一个具体的线程。它是一个线程用于存取本地变量 ThreadLocal.Values localValues;的操作类,localValues是一个map类型的数据,key就是ThreadLocal,value就是插入的数据,在一个线程中可以插入不同ThreadLocal实例的数据,一个线程本地变量只能缓存特定ThreadLocal实例的一条数据。

在java中ThreadLocal以Map的形式存储数据(ThreadLocal对象为 key 数值为value)。在Android中做了些改动,在Thread-Local的add方法中,可以看到它会把ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。

先看下android的ThreadLocal类的源码,其中也就两个接口方法重要,get()和set(T data);

ThreadLocal数据插入流程

//set(T data)让线程插入一个key为当前ThreadLocal实例,value为value的键值对
   public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
//values(Thread current)这里就可以看到其实ThreadLocal类操纵的还是当前线程的本地变量。
Values values(Thread current) {
        return current.localValues;
    }
   //然后对values判定是否为空,如果为空那么初始化一个空的Values实例,
   //如下图就是初始化了一个空的Values类实例复制给了当前线程的
   //ThreadLocal.Values localValues属性字段;
   Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
Values() {
            initializeTable(INITIAL_SIZE);
            this.size = 0;
            this.tombstones = 0;
        }
        /**
         * Creates a new, empty table with the given capacity.
         */
        private void initializeTable(int capacity) {
            this.table = new Object[capacity * 2];
            this.mask = table.length - 1;
            this.clean = 0;
            this.maximumLoad = capacity * 2 / 3; // 2/3
        }
//最后调用Values实例的put方法完成了数据插入到map中,可以清晰看到key为ThreadLocal类实例,value就是set方法传进来的数据。

//下面是Values类的put方法处理逻辑,看到for循环时候,寻找插入位置时候先匹配到key,而key存放的位置比较特殊在数组下标的0 ,2, 4 ,6 ... 2n;这些位置,value存放位置在1,3,5,7...2n+1这些位置。


  void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;
            //index是寻找key存放的下标, key.hash & mask寻找循环的起止位置,mask是table.length-1,默认是31,key.hash & mask计算后使得index一定指向key的下标。next()方法是对index加2操作。
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

ThreadLocal数据获取流程

//get获取数据
 public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);//获取到当前线程的本地变量map
        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对象,然后求出table数组ThreadLocal实例reference属性的下标。前文说过:ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。现在得到index后再index+1就是value在table数组中的下标。即value=table[index+1];return value即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值