ThreadLocal原理与使用

1.ThreadLocal内部结构

  下图是TreadLocal的基本数据结构:
ThreadLocal本地线程

          图1-ThreadLocal数据结构图

  其中对外公开的主要方法:
ThreadLocal调用方法
          图2-ThreadLocal外部可见方法

  我们使用该类时,一般是将该类作为某个类的静态成员变量重写其initialValue()方法,设置线程变量的初始值,以下是其的简单使用:

public class ThreadLocalDemo {
    private Integer count;
    public ThreadLocalDemo(Integer i){
        this.count=i;
    }
    //ThreadLocal的简单使用
    private static final ThreadLocal<ThreadLocalDemo> HOLDERS = new ThreadLocal<ThreadLocalDemo>() {
        @Override
        protected ThreadLocalDemo initialValue() {
            return new ThreadLocalDemo(0);
        }
    };

    /**
     * 每次get的时候计数增加1;
     */
   public static ThreadLocalDemo getDemo(){
       ThreadLocalDemo demo=HOLDERS.get();
        demo.count++;
       return demo;
   }
   public Integer getCount(){
       return this.count;
   }
}

    在多线程场景下使用该demo类:

package com.xin.thread;

/**
 * 多线程测试案例
 */
public class TestDemo {
    public static void main(String []args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while(i<5) {
                    System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalDemo.getDemo().getCount());
                    i++;
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while(i<5) {
                    System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalDemo.getDemo().getCount());
                    i++;
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while(i<5) {
                    System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalDemo.getDemo().getCount());
                    i++;
                }
            }
        }).start();
    }
}

执行结果如下所示:
在这里插入图片描述
  可以看到在多个线程同时使用ThreadLocalDemo.getDemo()方法的时候并没有发生我们可能预料的竞争,同一个线程内总是按顺序取得了计数值,不同线程间也没有发生争夺计数值的情况,不同线程总是独立执行的。似乎在demo类中定义的类静态变量的功能失效了,我们知道通常情况下,类变量(与成员变量相区分)总是被所有线程共享的。
  这里的疑惑之处在于:HOLDERS.get() 在没有给共享变量holder上锁的情况下,并没有产生线程之间竞争,进去这个方法可以看到:

public T get() {
        Thread t = Thread.currentThread();//1获取当前线程
        ThreadLocalMap map = getMap(t);//2,Thread类中存在ThreadLocal.ThreadLocalMap threadLocals = null的定义,通过该方法获取ThreadLocalMap,该类是TreadLocal类中的一个内部类,存储了
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//3
    }
    
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
 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;
    }
//创建ThreadLocalMap,该map以键值对的方式存储了当前线程与泛型变量;
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

  这里涉及到ThreadLocal的内部类ThreadLocalMap以及ThreadLocalMap的内部类Entry,由下图可知Entry主要是存储Treadlocal实例弱引用与变量值对的数据结构;

 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) {
            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[] table数组,在新建ThreadLocalMap实例中会将当前ThreadLocal弱引用和变量值存储在该数组中。
    回到ThreadLocal的get()方法:

public T get() {
        Thread t = Thread.currentThread();//1获取当前线程
        ThreadLocalMap map = getMap(t);//2,Thread类中存在ThreadLocal.ThreadLocalMap threadLocals = null的定义,通过该方法获取ThreadLocalMap,该类是TreadLocal类中的一个内部类,存储了
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//3
    }
    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;
    }

由于线程类中含有ThreadLocal.ThreadLocalMap threadLocals = null;属性,当根据当前线程获取该map的值为空(步骤1,2)时会创建该map(步骤3),当同一线程下一次获取类型为T的变量时,会在其threadLocals中查找是否存在与该线程对应的变量,直接以当前线程为key查找,若存在返回其值,可见用ThreadLocal包装的变量,每个线程在获取时就已经将其存储在自己的属性threadLocals中。有多个被ThreadLocal包装的变量时,同理都存储在该属性中。
关系描述可以用下图描述:
引用图

    每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例的弱引用,value是真正需要存储的Object。ThreadLocalMap是使用ThreadLocal的弱引用作为Key的,弱引用的对象在GC时会被回收。https://zhuanlan.zhihu.com/p/28049662

    对于多个ThreadLocal包装的变量,其关系如下图所示:
在这里插入图片描述
    可见该类的使用在多线程环境中是典型的以空间换时间。

2.为什么使用ThreadLocal?

  在使用ThreadLocal时,发现使用该类封装的变量与在各线程中使用局部变量完全可以达到相同的效果,为什么还会使用ThreadLocal呢?先看代码:

package threadlocaldemo;

public class ThreadLocalDemo {
    private static ThreadLocal<Integer>contextValue=new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public  Integer getCount(){
        int count=contextValue.get();
         count++;
        contextValue.set(count);
        return count;
    }
    public static void main(String []args){
        ThreadLocalDemo demo=new ThreadLocalDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int a0=1;
                while(true){
                    System.out.println(Thread.currentThread().getName()+demo.getCount());
                    System.out.println("a0:"+(a0++));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int a1=1;
                while(true) {
                    System.out.println(Thread.currentThread().getName()+demo.getCount());
                    System.out.println("a1:"+(a1++));

                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
       new Thread(new Runnable() {
            @Override
            public void run() {
                int a2=1;
                while(true) {
                    System.out.println(Thread.currentThread().getName()+demo.getCount());
                    System.out.println("a2:"+(a2++));
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

其中contextValue为ThreadLocal封装的变量,a0,a1,a2分别是线程局部变量,输出结果如下:

Thread-01
a0:1
Thread-11
a1:1
Thread-21
a2:1
Thread-02
a0:2
Thread-12
a1:2
Thread-22
a2:2
Thread-03
a0:3
Thread-13
a1:3
Thread-23
a2:3
Thread-14
a1:4
Thread-24
Thread-04
a0:4
a2:4
Thread-25
a2:5
Thread-05
a0:5
Thread-15
a1:5
Thread-16
a1:6
Thread-06
a0:6

    可以看到,两者效果一致,那么为什么还需要去使用ThreadLocal呢?我们写代码不能只是从效用上去考虑,我们还应该考虑代码的易读性,可维护性等因素;从上面的案例我们可以看到,同样的效果,一旦修改代码,ThreadLocal变量的使用和线程局部变量的使用显然不是一个体量。另外,从官方描述中可以看到,该类主要是为方便线程处理自己的状态而设立的,每个线程在获取该类变量的时候,都拥有它自己相对独立的变量初始化拷贝。

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

    一个ThreadLocal变量可以理解为全局变量,每个线程都只能读写自己线程的独立副本,互不干扰,解决了参数在一个线程中各个函数之间互相传递的问题。

3.使用ThreadLocal类存在的问题

    前面我们有说到,每个线程对ThreadLocal实例的引用是弱引用的key,而弱引用的对象的虚拟机垃圾回收的时候将会被回收,但是value值是被ThreadLocalMap强引用的,其生命周期与线程的生命周期相同。那么问题来了,如果value值很大,程序的线程本地变量使用较多,就会造成因无法及时回收而造成outofmemory的内存泄漏。庆幸的是,ThreadLocal为我们提供了remove()方法:

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
 
 private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
   //通过重新散列位于staleSlot和下一个null插槽之间的任何可能冲突的条目来清除陈旧的条目。 这还会清除尾随null之前遇到的所有其他过时的条目。
  private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }


该方法逻辑很简单,通过往后环形查找到与指定key相同的entry后,先通过clear方法将key置为null后,使其转换为一个脏entry,然后调用expungeStaleEntry方法将其value置为null,以便垃圾回收时能够清理,同时将table[i]置为null。

4.参考文档

https://www.jianshu.com/p/30ee77732843

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocalJava中的一个线程级别的变量,它提供了一种简单的方式来在多线程环境中维护变量的值。每个线程都拥有自己独立的ThreadLocal实例,并且可以通过该实例来获取和设置其对应的变量的值。 ThreadLocal原理是通过在每个线程中创建一个独立的副本来存储变量的值。这样,每个线程都可以独立地访问和修改自己的副本,而不会对其他线程产生影响。 ThreadLocal使用非常简单。首先,我们需要创建一个ThreadLocal对象,并指定要存储的变量类型。然后,我们可以通过调用ThreadLocal的get方法来获取当前线程中与该ThreadLocal对象关联的变量值,如果当前线程还没有设置过该变量,get方法会返回null。类似地,我们可以通过调用ThreadLocal的set方法来设置当前线程中与该ThreadLocal对象关联的变量值。 下面是一个简单的示例代码: ``` public class ThreadLocalExample { private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); public static void main(String[] args) { THREAD_LOCAL.set("Hello, world!"); Thread thread1 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 1!"); System.out.println(THREAD_LOCAL.get()); }); Thread thread2 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 2!"); System.out.println(THREAD_LOCAL.get()); }); thread1.start(); thread2.start(); System.out.println(THREAD_LOCAL.get()); } } ``` 在上面的示例中,我们创建了一个ThreadLocal对象`THREAD_LOCAL`用于存储String类型的变量。首先我们通过调用`THREAD_LOCAL.set("Hello, world!")`方法在主线程中设置了变量的值。然后,我们创建了两个新的线程并分别在其中设置了不同的值,并打印出来。最后,在主线程中我们也打印了变量的值。 运行上面的代码,你会看到输出结果类似于: ``` Hello from thread 1! Hello from thread 2! Hello, world! ``` 可以看到,每个线程都可以独立地访问和修改自己的变量副本,不会对其他线程产生影响。这样就确保了在多线程环境中,每个线程都可以维护自己的变量状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值