ThreadLocal

ThreadLoca,线程本地变量。它为每一个使用该变量的线程都提供了独立的副本,通过它可以做到线程间的数据隔离,每个线程都可以访问各自内部的副本变量。

它本身能够被多个线程共享使用,ThreadLocal类提供了get(),set()方法,在不同的线程中调用它的set方法进行设值,值是保存在当前线程自己的"map"中的。每个线程都只能获取到自己的"map",因此只有自己才可以通过get来获取设置的值。通过这种方式很好的做到线程间的数据隔离。

ThreadLocal的几个重要方法

  • set(T value) :将值保存到当前线程的map中。
  •  get() :从当前线程的map中获取值。
  • initialValue():我们可以在创建ThreadLocal时重写该方法提供一个初始值,当没有set就直接get时候就获取该初始值。
  • remove() 移除当前线程保存在map中的值。 

 使用:

public class Main {

    public static void main(String[] args) {
        ThreadLocal threadLocal=new ThreadLocal(){
            @Override
            protected Object initialValue() {
                return 0;//提供一个初始值
            }
        };
       new Thread(new Runnable() {
           @Override
           public void run() {
               threadLocal.set(100);
               System.out.println("获取"+Thread.currentThread().getName()+"保存的值:"+threadLocal.get());
           }
       }).start();

        System.out.println("获取"+Thread.currentThread().getName()+"保存的值:"+threadLocal.get());
    }


结果:

获取main保存的值:0
获取Thread-0保存的值:100

可以看到ThreadLocal的使用很简单,通过它的set方法我们可以进行设值,通过get方法可以获取set方法保存的值,各个线程通过ThreadLocal get到的值都是各自调用set保存的值,它们互不干扰,很好的做到了线程间的数据隔离。同样的,如果我们没有经过set就直接get获取到的就是我们重写的initialValue方法提供的初始值,如果我们不重写它,初始值就是null。

如果在一个线程中多次调用set方法,然后调用get方法会如何?

ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set(100);
        threadLocal.set(200);
        threadLocal.set(300);
        System.out.println(threadLocal.get());  //300

结论:get方法总是返回当前线程在调用set时设置的最新值。

通过源码看本质:
先看看threadLocal.set方法是如何保存数据的?

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

首先,获取到当前的线程对象,然后调用getMap方法获取当前线程对象的ThreadLocalMap,它是Thread类的一个成员,完全类似于HashMap,因此可以把它看作一个"map"。

//ThreadLocal#getMap
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

如果获取到map,就调用它的set方法设值,以ThreadLocal对象为key。

如果map为null(第一次设值),就调用createMap方法创建一个"map",然后就值保存进去,以ThreadLocal对象为key。

如何通过createMap方法创建一个"map"呢?

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

可以看到,直接创建一个ThreadLocalMap对象,然后将它赋给当前线程的threadLocals成员。在创建"map"的时候同时就通过构造将值保存进去了。

ThreadLocalMap是ThreadLocal的静态内部类。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

ThreadLocalMap内部又是通过Entry来保存数据的。

当map不为null就不用创建它了,会直接调用map.set(this, value)进行设值,看看它怎么做的?

    private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {//1、如果键相同,就使用新数据替换旧数据
                    e.value = value;
                    return;
                }

                if (k == null) {//2、如果有Entry的key为null,就将其逐出,并用新数据替换。
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            
            //创建新的Entry,并且用ThreadLocal作为key,要保存的数据作为value
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

注释1解释了为什么get方法总是返回当前线程在调用set时设置的最新值,注释2是为了防止内存泄漏的。

threadLocal.set方法理解起来很简单,本质就是将数据保存到当前线程的"map"中,以ThreadLocal为key。每个线程都有自己的map成员,这样当然可以很好的做到线程间的数据隔离。

再来看看是如何获取保存的值?

//ThreadLocal#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();
    }

很简单,首先获取当前的线程对象,然后调用getMap获取到当前线程对象持有的ThreadLocalMap,然后以ThreadLocal为key从"map"中获取Entry,前面已经知道Entry保存了我们的数据,获取到就直接返回。如果map为null或者Entry为空就需要调用setInitialValue()方法获取一个初始值。

//ThreadLocal #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;
    }

先调用initialValue获取初始值,initialValue方法我们可以在创建ThreadLocal时重写,然后将初始值保存到map中,最后返回初始值。

补充:ThreadLocalMap通过Entry来保存数据,Entry是WeakReference的子类,从构造可以看出只有key是弱引用的,当JVM开始垃圾回收时,如果ThreadLocal和GC ROOTS之间不存在引用链就会被回收,此时key==null,但是需要注意value还是存在的。 下次再set数据时会进行检查,删除key为空的Entry,可以从一定程度上防止内存泄漏。比较好的习惯是用完后调用remove把数据移除,否则可能会产生泄漏,只有当前线程结束时,数据才会被释放。

     static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

至于ThreadLocal的remove方法,就是直接将"map"中的数据移除。

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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值