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方法,清除数据,防止内存泄漏。

  • 相关阅读:
    【leetcode】1630. Arithmetic Subarrays
    【leetcode】1629. Slowest Key
    【leetcode】1624. Largest Substring Between Two Equal Characters
    【leetcode】1620. Coordinate With Maximum Network Quality
    【leetcode】1619. Mean of Array After Removing Some Elements
    【leetcode】1609. Even Odd Tree
    【leetcode】1608. Special Array With X Elements Greater Than or Equal X
    【leetcode】1603. Design Parking System
    【leetcode】1598. Crawler Log Folder
    Java基础加强总结(三)——代理(Proxy)Java实现Ip代理池
  • 原文地址:https://www.cnblogs.com/bl123/p/14142650.html
Copyright © 2011-2022 走看看