zoukankan      html  css  js  c++  java
  • ThreadLocal作用以及原理解析

    ThreadLocal作用

    对于多个线程访问一个共享变量的时候,我们往往要通过加锁的方式进行同步,像这样

    但是除此之外,其实还有另一种方式可以隔绝线程对于共享变量读写的独立性。那就是ThreadLocal。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有一块独立的空间,当多个线程操作这个变量的时候,实际上操作的都是自己线程所属的空间的那个变量,不会对其他线程有影响,也不会被其他线程影响,因为彼此都是互相独立的。因此想要保证线程安全,也可以把共享变量放在ThreadLocal中。总体来说就是,ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

    接下来看一个例子

    public class ThreadLocalDemo1 {
    
        public static int value = 0;
       static ThreadLocal<Object> local = new ThreadLocal<>();
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+"---线程初始值:"+local.get());
                        local.set("我是"+Thread.currentThread().getName());
                        System.out.println(Thread.currentThread().getName()+"---线程修改值:"+local.get());
                    }
                },"线程"+i).start();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程"+local.get());
        }
    
    }
    
    

    运行结果

    线程1---线程初始值:null
    线程4---线程初始值:null
    线程3---线程初始值:null
    线程0---线程初始值:null
    线程2---线程初始值:null
    线程0---线程修改值:我是线程0
    线程3---线程修改值:我是线程3
    线程4---线程修改值:我是线程4
    线程1---线程修改值:我是线程1
    线程2---线程修改值:我是线程2
    主线程null
    

    上面这段代码,运行结果印证了我们开头说的那些关于ThreadLocal的论述,分析一下代码,可以看到,我们这里只有一个ThreadLocal对象,即local,我们一共有五个线程,线程0对local进行set值之后,线程2再get却还是null,但是线程0自己再get,却可以拿到自己设置的那个值 我是线程0 ,别的线程是拿不到这个值的,而且代码的最后,在所有的线程都运行完毕之后,在主线程对local进行get操作,拿到的值却还是null。这就印证了这个结论,每个线程都只能拿到自己线程所属的值,线程之间是相互独立的

    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
                //如果当前线程第一次设置值 那就创建一个存储区域(Map)
                createMap(t, value);
        }
    

    继续看看 createMap(t, value)方法的源码

     /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    根据注释,和源码里面可以看到,createMap方法里面实际上就是new了一个ThreadLocalMap对象,创建了一个与ThreadLocal关联的map,然后把当前Thread中的一个ThreadLocal引用和要设置的值放进去。请注意t.threadLocals是定义在Thread类里的,代码如下

      //这一段是定义在Thread类中的
    ThreadLocal.ThreadLocalMap threadLocals = null;
    

    继续看看ThreadLocalMap里有啥

      /**
             * Construct a new map initially containing (firstKey, firstValue).
             * ThreadLocalMaps are constructed lazily, so we only create
             * one when we have at least one entry to put in it.
             */
            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数组,将Thread里面的ThreadLocal.ThreadLocalMap变量引用并且通过一系列的hashcode操作,作为key。接下来继续看看Entry

        /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收。

    以上就差不多是整个set方法的源码了,有了以上的了解,再看get的就会很简单

    get方法

      public T get() {
          //获取当前线程,并以线程引用为key去ThreadLocalMap中获取值
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
          //如果为不为空,就以当前线程对应的Thread中的ThreadLocal引用 进行hash计算 拿到值返回
            if (map != null) {
                //拿到引用去获取值
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
          //为空就返回一个初始值
            return setInitialValue();
        }
    
    //对应getMap方法 返回的是当前线程的threadLocals引用
      ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    //上面的getEntry方法
          private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    
    //get中的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;
        }
    
    
    

    可以看到,如果你一个线程在get之前没有set,也是会给你以当前线程创建一个与你线程对应的createMap的,只不过key是当前线程下的ThreadLocalMap引用,value为null,因为

     T value = initialValue();
    

    这一行的的initialValue()方法是这样的

      protected T initialValue() {
            return null;
        }
    

    其实还有remove方法啥的,原理都差不多.。所以就不赘述了。

    总结

    ThreadLocal为每个线程都定义了一个ThreadLocalMap类型名为threadLocals的变量,有点类似于HashMap,但是严格意义上来说不是,只是将其类比为Map结构,key为当前线程中的threadLocals的this引用,value是设置的值。所以每个线程都有不同的key,所能获取到的值也是不一样的,就是利用这种思想去保证值对每个线程的独立性。因为不管是get还是set之前都会有currentThread这个操作,所以每个线程都只能取到自己线程对应的值。

  • 相关阅读:
    Delphi中多标签页面的实现
    选择排序
    关于Delphi中TRttiContext.FindType失效的问题
    Delphi中拖动无边框窗口的5种方法
    集中精力做最有价值的事情,而不必把主要精力都浪费在自我包装上(例如学位,头衔,自吹自擂)——沉痛反思:我以前还真是这样
    QModelIndex有internalPointer()函数,可以存任何数据,另有QAbstractItemModel::createIndex来创造节点
    沉没成本——无法收回的成本,但不要影响下一次决策
    使用HttpURLConnection实现多线程下载
    Delphi6/7 中XML 文档的应用
    delphiXE调用Objective-c库
  • 原文地址:https://www.cnblogs.com/blackmlik/p/12923319.html
Copyright © 2011-2022 走看看