美文网首页
九、Java高级特性(ThreadLocal的使用和原理分析)

九、Java高级特性(ThreadLocal的使用和原理分析)

作者: 大虾啊啊啊 | 来源:发表于2021-05-30 20:50 被阅读0次

    一、前言

    我们在前面学习知道对象和变量的并发访问我们可以使用synchronized、volatile关键字。synchronized保证了数据的原子性,解决并发访问同一数据的时候排队执行。volatile保证了数据的可见性,使得在多线程共享一个数据的时候,一旦数据发生改变。各个线程都能拿到最新的数据的之。
    而ThreadLocal的出现使得每个线程都有自己的一份副本,也就是使得每个线程都单独拥有自己的数据。达到线程之间的数据隔离。

    二、ThreadLocal的使用

    package com.it.test.thread;
    
    public class ThreadLocalDemo {
        static ThreadLocal<Integer> local1 = new ThreadLocal<>() {
            @Override
            protected Integer initialValue() {
                return 1;
            }
        };
    
        public static void main(String[] args) {
            new Thread1().start();
            new Thread2().start();
        }
    
       static class Thread1 extends Thread {
            @Override
            public void run() {
    
                local1.set(1);
                System.out.println(Thread.currentThread().getName()+":"+local1.get());
    
            }
        }
    
        static class Thread2 extends Thread {
            @Override
            public void run() {
                local1.set(2);
                System.out.println(Thread.currentThread().getName()+":"+local1.get());
            }
        }
    }
    
    
    Thread-0:1
    Thread-1:2
    

    三、ThreadLocal源码分析

    1、set方法

    获取当前线程,拿到当前线程的ThreadLocalMap ,如果map为空,则创建一个map,否则之间给当前map设置值,key为当前ThreacLocal对象,值就是我们存放的值。

     public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                map.set(this, value);
            } else {
                createMap(t, value);
            }
        }
    

    来看下createMap方法,为当前线程设置了一个ThreadLocalMap对象,

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

    来看下ThreadLocalMap的源码,包含了一个静态内部类Entry,继承了弱引用的ThreadLocal。

       static class ThreadLocalMap {
    
      
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    在上面为线程创建一个ThreadLocalMap对象的时候,调用了ThreadLocalMap的构造方法,在ThreadLocalMap的构造方法,构造方法传入了:
    ThreadLocal<?> firstKey, Object firstValue两个参数。
    接着内部创建了内部类的Entry类型数组,
    table = new Entry[INITIAL_CAPACITY];
    然后创建Entry对象,传入firstKey和firstValue,存入到Entry数组中。
    这里需要注意的是,之所以创建了一个Entry数组,是因为一个线程可能有多个ThreadLocal对象,
    每一个ThreadLocal,都会在ThreadLocalMap内部的Entry数组存入。

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

    也就是当我们在某个线程里通过ThreadLocal设置值的时候,那么就会在当前线程中创建一个ThreadLocalMap,如果存在ThreadLocalMap则直接获取当前线程的ThreadLocalMap。而ThreadLocalMap包含一个Entry数组,Entry有两个属性key和value。使用ThreadLocal设置值的时候,就会把ThreadLocal对象作为key,以及具体设置的值,初始化给Entry对象,然后存入Entry类型的数组中。当有多个
    ThreadLocal的时候,数组就会存入多个Entry对象。

    2、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();
        }
    

    首先拿到当前线程,然后获取当前线程的ThreadLocalMap 。调用ThreadLocalMap 的getEntry方法。

       private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.refersTo(key))
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    

    我们知道ThreadLocalMap 中的包含了一个Entry数组,getEntry方法,传入了ThreadLocal对象作为key,然后经过系列的位运算,算出数组的下标。最好从数组中拿到Entry对象,

       ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
    

    拿到Entry对象,直接获取Entry对象里的value值,就是我们存入的value值。

    小结

    • ThreadLocal使得每个线程拥有自己的一份副本,也就是拥有自己的一份数据。达到线程直接数据隔离
    • ThreadLocal的实现原理的核心就是:
      (1)当我们在某个线程下第一次使用ThreadLocal设置值的时候,那么就会为这个线程创建一个ThreadLocalMap 对象,ThreadLocalMap 中包含了一个Entry数组。Entry中包含了两个属性key和value。
      在设置值的时候,就会将我们的值存放到ThreadLocalMap 的Entry数组中,其中key为当前ThreadLocal对象,值就是我们设置的值。
      (2)当我们使用ThreadLocal获取值的时候,首先是会获取当前线程的ThreadLocalMap ,然后获取ThreadLocalMap 中的数组。接着通过当前ThreadLocal作为key,通过位运算获取到属性key为当前ThreadLocal对象的Entry对象,在获取Entry中的value属性。就是我们需要获取的值。
    image.png

    相关文章

      网友评论

          本文标题:九、Java高级特性(ThreadLocal的使用和原理分析)

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