• Java并发编程(七)-- ThreadLocal


    提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和认识,希望让大家理解ThreadLocal更加透彻一些。

    ThreadLocal是什么

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

    通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。

    用法简介

    创建,支持泛型

    ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();

    你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。

    set方法

    mStringThreadLocal.set("jackPotHan");
     

    get方法

    mStringThreadLocal.get();
     

    初始化

    由于ThreadLocal对象的set()方法设置的值只对当前线程可见,那有什么方法可以为ThreadLocal对象设置的值对所有线程都可见。

    为此,我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。如下所示:

    private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
       @Override protected String initialValue() {
           return "This is the initial value";
       }
    };

    此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。

    完整的使用示例

    public class ThreadLocalExample {
        public static class MyRunnable implements Runnable {
     
            private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    
            @Override
            public void run() {
                threadLocal.set( (int) (Math.random() * 100D) );
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println(threadLocal.get());
            }
        }
    
     
        public static void main(String[] args) {
            MyRunnable sharedRunnableInstance = new MyRunnable();
     
            Thread thread1 = new Thread(sharedRunnableInstance);
            Thread thread2 = new Thread(sharedRunnableInstance);
     
            thread1.start();
            thread2.start();
    
            thread1.join(); //wait for thread 1 to terminate
            thread2.join(); //wait for thread 2 to terminate
        }
     
    }
        
     

    上面创建了两个线程共享一个MyRunnable实例。每个线程执行run()方法的时候,会给同一个ThreadLocal实例设置不同的值。如果调用set()方法的时候用synchronized关键字同步,而且不是一个ThreadLocal对象实例,那么第二个线程将会覆盖第一个线程所设置的值。

    然而,由于是ThreadLocal对象,所以两个线程无法看到彼此的值。因此,可以设置或者获取不同的值。

    如何实现

    为了更好的掌握ThreadLocal,我认为了解其内部实现是很有必要的,这里我们以set方法从起始看一看ThreadLocal的实现原理。

    下面是ThreadLocal的set方法

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

    首先获取当前线程

    利用当前线程作为句柄获取一个ThreadLocalMap的对象

    如果上述ThreadLocalMap对象不为空,则设置值,否则创建这个ThreadLocalMap对象并设置值

    下面是一个利用Thread对象作为句柄获取ThreadLocalMap对象的代码

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    //实际上是Thread对象的threadLocals变量
    class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
    //如果一开始设置,即ThreadLocalMap对象未创建,则新建ThreadLocalMap对象,并设置初始值。
    void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

    从上面可以看出:实际上ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。

    对象存放在哪里

    在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

    问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?

    其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。

    它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。

    关于堆和栈的比较,请参考Java中的堆和栈的区别

    InheritableThreadLocal

    真的只能被一个线程访问么?既然上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?

    的确,ThreadLocal的值只能被当前线程访问,其他线程无法对其进行访问、操作,但是Inheritable ThreadLocal可以实现对其他线程甚至是父线程的访问。

    InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。

    下面我们分析一下InheritableThreadLocal的实现细节,下面展示了InheritableThreadLocal提供的方法:

    InheritableThreadLocal继承了ThreadLocal,然后重写了上面三个方法,所以除了上面三个方法之外,其他所有对InheritableThreadLocal的调用都是对ThreadLocal的调用,没有什么特别的。

    会导致内存泄露么

    有网上讨论说ThreadLocal会导致内存泄露,原因如下

    首先ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有。
    如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活
    所以,ThreadLocal设定的值被持有,导致内存泄露。
    上面的逻辑是清晰的,可是ThreadLocal并不会产生内存泄露,因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。

    参考资料:

      https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/

  • 相关阅读:
    eclipse 配置SVN代理服务器
    jenkins 配置SVN 代理服务器
    记录服务器启动redis过程
    java牛客刷题2020年9月4日
    java牛客网错题2020年9月3日
    bootstrap-select 实现搜索,如果内容搜索不到显示到框内
    pandas教程5-合并 concat
    pandas教程-4导入导出
    pandas简单教程1
    AttributeError: module 'pandas' has no attribute 'Series'
  • 原文地址:https://www.cnblogs.com/JackpotHan/p/9668770.html
Copyright © 2020-2023  润新知