美文网首页
Java-ThreadLocal

Java-ThreadLocal

作者: Android_Gleam | 来源:发表于2020-09-28 14:50 被阅读0次

定义

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

使用示例

public class ThreadLocalTest {

    private static ThreadLocal<String> 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<T> {
    private Map<Thread, T> 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<String> 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<ThreadLocal<?>> {
            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

    Java-ThreadLocal 参考 A Painless Introduction to Java's Thr...

  • 简述Java中的ThreadLocal使用方法

    本文为译文,原文链接:https://www.baeldung.com/java-threadlocal。原文简单...

  • Java-ThreadLocal

    简述 一种线程绑定机制,每个线程都拥有对象的独立副本,不会与其他线程冲突,从而避免了并发与同步问题。 结构 每个线...

  • Java-ThreadLocal

    定义 线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal可以让每个线程拥有一个属于自...

  • java-threadlocal示例

    ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道T...

  • Java-ThreadLocal详解

    1、ThreadLocal的作用为:为每一条线程分配独立的资源,与synchronized方式不同,ThreadL...

  • JAVA-ThreadLocal浅析

    概述 ThreadLocal如果单纯从名字上来看像是“本地线程"这么个意思,只能说这个名字起的确实不太好,很容易让...

  • 解读ThreadLocal-尽量一篇包含所有疑问

    java-ThreadLocal ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值...

  • 深入剖析Java-ThreadLocal原理

    1、ThreadLocal是什么 ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存...

  • Java-ThreadLocal虚引用取值问题分析

    Threadlocal是为了,解决多线程环境下变量隔离的问题; ThreadLocalMap 是threadLoc...

网友评论

      本文标题:Java-ThreadLocal

      本文链接:https://www.haomeiwen.com/subject/mjnzyktx.html