Java-ThreadLocal

定义

线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal可以让每个线程拥有一个属于自己的变量的副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离

使用示例

public class ThreadLocalTest {

    private static ThreadLocal threadLocal = new ThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

通过上面的代码我们发现,当我我们使用同一个ThreadLocal对象在不同线程中set不同的值,会打印出不同的值,这是怎么做到的呢?上面定义中提到每个线程拥有一个属于自己的变量的副本,这就能解通为什么我们可以获取到不同的值。

既然每个线程有一个自己的变量副本,那我们自己是不是也可以实现呢?答案是肯定的,下面我们自己实现一个类似的功能。

//自定义实现的ThreadLocal
public class MyThreadLocal {
    private Map hashMap = new HashMap();
    
    //多线程下保证原子性
    public synchronized void set(T t) {
        synchronized (MyThreadLocal.this) {
            hashMap.put(Thread.currentThread(), t);
        }
    }

    public synchronized T get() {
        return hashMap.get(Thread.currentThread());
    }
}

public class ThreadLocalTest {
    //使用我们自己定义的ThreadLocal
    private static MyThreadLocal threadLocal = new MyThreadLocal<>();

    static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            threadLocal.set("son");
            System.out.println(threadLocal.get());  //son
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        threadLocal.set("main");
        System.out.println(threadLocal.get());  //main
    }
}

我们自己实现了一个MyThreadLocal对象,创建了一个Key是Thread的Map对象存储对应线程的数据,也实现了ThreadLocal的功能,下面我们看看系统的ThreadLocal是怎么做的。

源码分析

我们从set方法开始分析。

    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        //获取ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null) //不为空直接调用ThreadLocalMap的set方法
            map.set(this, value);  
        else
            createMap(t, value);  //为空创建ThreadLocalMap 并将数据存储起来
    }

然后跟进getMap方法

ThreadLocalMap getMap(Thread t) {
        //这里调用到了Thread中,也就是说ThreadLocalMap对象是从Thread中获取的
        return t.threadLocals;
    }

继续看看Thread的threadLocals对象

    //Thread中定义的threadLocals属性
    ThreadLocal.ThreadLocalMap threadLocals = null;

继续看看ThreadLocalMap的set方法

 private void set(ThreadLocal key, Object value) {
            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();
                //如果存在key则更新值
                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //没有对应的key则添加到数组中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

上面我们看到,存值的时候将ThreadLocal和Object(我们存储的数据)封装到了一个Entry对象中,下面我们看下这个Entry

//ThreadLocalMap中的内部类 存储了ThreadLocal和我们保存的值
static class Entry extends WeakReference> {
            Object value;
            //ThreadLocal作为key
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

这里我们可以总结下大致的流程
1、ThreadLocal调用set方法,然后获取当前调用的Thread
2、根据Thread获取threadLocals属性(ThreadLocalMap)
3、调用ThreadLocalMap的set方法
4、将ThreadLocal和我们保存的值封装成Entry对象,添加到数组(table)中

我们知道了存储的大致流程,获取其实也是一样的,都是对应的对象调用流程,我们就不介绍了。

我们上面也实现了自己的MyThreadLocal对象,也完成了线程副本数据的存储,我们思考下,这两种方式有什么区别呢?系统为什么要做这么多操作步骤去实现线程副本数据呢?

我们自己的实现方式是创建了一个Map去存储,当多线程情况下,为了保证原子性,我们加了锁。
如果当前有很多线程要获取数据,那么这些线程都会去争夺这个map,没有拿到的就会阻塞,这样性能上肯定是会有影响的。

系统的实现方式则是,没个线程都有自己的map,不存在并发的问题,自己用自己的,效率无疑是要高的,所以系统的实现还是有一定道理的。

这就跟打篮球是一样的,大家都抢一个球,和每个人都有一个球,效果肯定是不一样的。

你可能感兴趣的:(Java-ThreadLocal)