zoukankan      html  css  js  c++  java
  • ThreadLocal类说明

    1.简介

      ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。  

      ThreadLocal是一个关于创建线程局部变量的类。

      通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。相当于线程的private static类型变量

      ThreadLocal的优点:

    • 提供线程内的局部变量:每个线程都自己管理自己的局部变量,互不影响。

      ThreadLocal的缺点:

    • 内存泄露问题:在ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时value和value指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!

      ThreadLocal的使用场景:

    • 实现单个线程单例以及单个线程上下文信息存储,比如通用id等。
    • 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。
    • 承载一些线程相关的数据,避免在方法中来回传递参数。

    2.使用示例

      示例代码如下

    public class Test {
        private static String strLabel;
        private static ThreadLocal<String> threadLabel = new ThreadLocal<String>();
        public static void main(String[] args) throws Exception {
            strLabel = "开始";
            threadLabel.set("开始");
            new Thread(new Runnable() {
                public void run() {
                    strLabel = "结束";
                    threadLabel.set("结束");
                }
            }).start();
            Thread.sleep(3000);
            System.out.println("strLabel = " + strLabel);
            System.out.println("threadLabel = " + threadLabel.get());
        }
    }
    View Code

      运行结果

    strLabel = 结束

    threadLabel = 开始

      从上面的示例可以看出,对于ThreadLocal类型的变量,在一个线程中设置值,不影响其在其它线程中的值。也就是说ThreadLocal类型的变量的值在每个线程中是独立的。

    3.ThreadLocal的实现

      ThreadLocal是构造函数只是一个简单的无参构造函数,并且没有任何实现。下面说下ThreadLocal的方法。

    (1)set方法

      源码如下

    public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null)
            map.set(this, value);//将value保存到 ThreadLocalMap中,并用当前ThreadLocal作为 key
        else
            createMap(t, value);//创建一个ThreadLocalMap并给到当前线程,然后保存 value
    }
    View Code

    (2)get方法

      源码如下

    public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//获取 key为当前 ThreadLocal的值
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();//调用setInitialValue()方法返回初始值,并保存到新创建的ThreadLocalMap中。
    }
    View Code

    (3)setInitialValue方法

      源码如下

    private T setInitialValue() {
        T value = initialValue();//设置ThreadLocal的初始值,默认返回 null
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    View Code

    (4)remove方法

       源码如下

    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());//获取当前线程的ThreadLocalMap
        if (m != null)//不为null,则移除
            m.remove(this);
    }
    View Code

    4.ThreadLocalMap

      ThreadLocalMap是ThreadLocal的内部类。

    (1)构造方法

      源码如下

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

      构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。

      ThreadLocal中当前线程的ThreadLocalMap为null时会使用ThreadLocalMap的构造方法新建。

    (2)存储结构

       源码如下

    // 初始容量,必须是 2 的幂
    private static final int INITIAL_CAPACITY = 16;
    
    // 存储数据的哈希表
    private Entry[] table;
    
    // table 中已存储的条目数
    private int size = 0;
    
    // 表示一个阈值,当 table 中存储的对象达到该值时就会扩容
    private int threshold;
    
    // 设置 threshold 的值
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    View Code

      ThreadLocalMap内部维护了一个哈希表(数组)来存储数据,并且定义了加载因子。

      table是一个 Entry类型的数组,Entry是ThreadLocalMap的一个内部类。

    (3)存储对象

        源码如下

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

      Entry用于保存一个键值对,其中 key以弱引用的方式保存。

    (4)内存泄露

      在ThreadLocalMap的set、get、remove方法中,都有清除无效Entry的操作,这样做是为了降低内存泄漏发生的可能。

      Entry中的key使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。

      注意:使用ThreadLocal的时候,每次用完ThreadLocal都调用remove方法,清除数据,防止内存泄漏。

  • 相关阅读:
    PAT——1069. 微博转发抽奖
    PAT——1068. 万绿丛中一点红
    PAT——1066. 图像过滤
    PAT——1065. 单身狗
    PAT——1064. 朋友数
    PAT——1063. 计算谱半径
    PAT——1062. 最简分数
    PAT——1061. 判断题
    PAT——1060. 爱丁顿数
    PAT——1059. C语言竞赛
  • 原文地址:https://www.cnblogs.com/bl123/p/14142650.html
Copyright © 2011-2022 走看看