`
bolinyang
  • 浏览: 74337 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JAVA线程本地存储之ThreadLocal的分析

阅读更多
一.概述

    ThreadLocal是JDK的一个线程本地存储的类,我们可以把一些线程私有的数据写在ThreadLocal中,这样这些数据只有一个线程可见,实现了所谓的栈封闭。这样存储一些线程私有的数据,我们就不用去费心考虑如何保证临界资源的互斥访问了,同时对于一个线程,这些私有数据也只做一次初始化。

二.一段ThreadLocal的测试代码
class LocalObject {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

class LocalStoreThread extends Thread {
    /** 定义了一个线程本地存储的成员变量 **/
    private ThreadLocal<LocalObject> threadLocal = new ThreadLocal<LocalObject>();

    public LocalStoreThread(LocalObject lo) {
        threadLocal.set(lo);
    }

    @Override
    public void run() {
        System.out.println(threadLocal.get().getName());
    }
}

/**
 * <pre>
 * 创建一个线程实例,这个线程实例中有一个线程本地存储成员变量
 * </pre>
 */
public class ThreadLocalTest {
    public static void main(String[] args) {
        LocalObject lo = new LocalObject();
        lo.setName("thread-local");
        new LocalStoreThread(lo).start();
    }
}



上述代码运行的时候在run方法中抛出了空指针异常,明明在构造函数中调用了threadLocal的set方法,为什么get的时候获取到了null,然后使用了null抛出了NPE呢?



由于ThreadLocal是和线程相关的,我们上面的代码在够在函数中往线程本地存储变量中设置了一个实例对象,在run方法中获取这个实例对象的时候发现拿到是null,所以我们有必要看一下set时对应的线程和get时对应的线程是不是一样的。因此在set之前打印一下Thread.currentThread(),同时在get之前打印一下Thread.currentThread()

class LocalObject {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

class LocalStoreThread extends Thread {
    /** 定义了一个线程本地存储的成员变量 **/
    private ThreadLocal<LocalObject> threadLocal = new ThreadLocal<LocalObject>();

    public LocalStoreThread(LocalObject lo) {
        // set之前打印当前线程
        System.out.println(Thread.currentThread().getName());   // main
        threadLocal.set(lo);
    }

    @Override
    public void run() {
        // get之前打印当前线程
        System.out.println(Thread.currentThread().getName()); // Thread-0
        System.out.println(threadLocal.get().getName());
    }
}

/**
 * <pre>
 * 创建一个线程实例,这个线程实例中有一个线程本地存储成员变量
 * </pre>
 */
public class ThreadLocalTest {
    public static void main(String[] args) {
        LocalObject lo = new LocalObject();
        lo.setName("thread-local");
        new LocalStoreThread(lo).start();
    }
}


好,问题出现了,set时的当前线程和get时的当前线程不一样,所以get的结果是null。set是写在线程的构造函数中的,此时当前线程是main线程,因为在main线程中创建线程。但是在run方法中当前线程已经不是main线程了变成了new出来的这个新线程了。

三.ThreadLocal中get和set方法分析
public T get() {
        // 获取当前线程实例
        Thread t = Thread.currentThread();
        /* 获取当前线程实例的ThreadLocalMap,其实就是一个数组
         * 这个数组可以扩容,每次空间不够时都拿当前大小*2
         */
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            /*根据this哈希获取数组中的一个元素*/
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        // 如果当前线程的ThreadLocalMap为null,就创建一个
        return setInitialValue();
}
private T setInitialValue() {
        /* 这里调用了ThreadLocal的initValue方法,一般都会在创建ThreadLocal
         * 实例的时候重写这个方法,比如说ThreadLocal中要是存放数据库链接对象的
         * 话,就需要一个初始化方法来初始化这个数据库链接对象
         */
        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*/
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/*获取线程的ThreadLocalMap*/
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}


上述代码总结


上述代码就是ThreadLocal的get源代码,先根据当前线程获取当前线程的ThreadLocalMap,此时获取到的就是一个table数组,接下来根据ThreadLocal实例的threadLocalHashCode来获取table数组中的一个元素,这个元素是个K-V的键值对,此时V就是本地存储的值。

/*关于set代码和get代码是对称的*/
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

四.ThreadLocal源代码中提供的一个实例代码
import java.util.concurrent.atomic.AtomicInteger;

public class UniqueThreadIdGenerator {
    private static final AtomicInteger uniqueId = new AtomicInteger(0);  

    private static final ThreadLocal < Integer > uniqueNum =   
            new ThreadLocal < Integer > () {  
        //定义初始值(副本)  
        @Override protected Integer initialValue() {  
            return uniqueId.getAndIncrement();  
        }  
    };  

    public static int getCurrentThreadId() {  
        // 这里应该要把 uniqueId换成uniqueNum,源码应该是写错了   
        return uniqueNum.get();  
    }

    static class MyThread extends Thread {

        @Override
        public void run() {
            System.out.println(UniqueThreadIdGenerator.getCurrentThreadId());
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 5; ++i) {
            new MyThread().start();
        }
    }
}


这里总共创建了5个线程,每个线程在run方法中调用UniqueThreadIdGenerator.getCurrentThreadId()时,发现每个线程的ThreadLocalMap都是null,所以每次初始化的方法initialValue都会被调用。

五.Thread&&ThreadLocal&&ThreadLocalMap之间的关系



总结



一个线程只有一个ThreadLocalMap,其实ThreadLocalMap就是一个table数组,数组中的每个元素都是一个K-V的键值对,其中K就是ThreadLocal实例,在获取本地存储的值的时候根据ThreadLocal实例的threadLocalHashCode来对table进行Hash查找,找到对应的K-V键值对。一个线程可以有多个ThreadLocal实例,那么有多少个ThreadLocal实例就决定了table数组的大小,这个数组是动态增长的,每次要是大小不够,就自动扩充为原来大小的2倍,然后对于原来的元素重新Hash,这个操作的成本还是很大的。
  • 大小: 31.6 KB
分享到:
评论

相关推荐

    java线程学习笔记

    2.3 线程本地存储(Java.lang.ThreadLocal) 15 2.4 线程阻塞 17 2.4.1 调用sleep(millisecond)使任务进入休眠状态 17 2.4.2 等待输出与输入 17 2.4.3 对象锁不可用 17 2.4.4 通过wait()使线程挂起。 17 2.5 线程...

    Java ThreadLocal详解_动力节点Java学院整理

    ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,本文会详细的介绍一下,有兴趣的可以了解一下

    JVM的基础和调优【JMM 内存结构 GC OOM 性能调优 ThreadLocal】

    每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程读/写共享变量的副本 本地内存时JMM的一个抽象概念, 并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的 硬件和编译器优化

    美团面试,问了ThreadLocal原理,这个回答让我通过了

    ThreadLocal是线程的内部存储类,可以在指定线程内存储数据。只有指定线程可以得到存储数据。 /** * This class provides thread-local variables. These variables differ from * their normal counte

    Java初级、中级、高级面试题及答案

    事务\事务隔离级别\Mysql默认隔离级别\串行化\存储引擎Innodb\Myisam\Inodb锁机制\MVCC\B树索引\哈希索引\聚簇索引\非聚簇索引\回表查询和覆盖索引\Explain语句\SQL语句的执行过程\范式\聚合函数\SQL优化\HTTP\多态\...

    疯狂JAVA讲义

    第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...

    java随机数

    另一个值得考虑的是多线程java.lang.ThreadLocal的实例。偷懒的做法是通过Java本身API实现单一实例,当然你也可以确保每一个线程都有自己的一个实例对象。 虽然Java没有提供一个很好的方法来管理java.util.Random的...

    SpringBoot实现动态切换数据源(含源码)

    `ThreadLocal` 是 Java 中的一个类,用于存储线程局部变量。线程局部变量与普通的变量不同,它不是共享的,每个线程都有其自己的独立的线程局部变量副本。这使得我们可以在多线程环境中为每个线程提供独立的变量副本...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【线程】ThreadLocal的作用 90 【Spring】什么是IOC和DI?DI是如何实现的 91 【Spring】spring中的IOC(控制反转)的原理 92 【Spring】什么是AOP 92 【Spring】Spring事务机制 93 声明式事物 93 编程式事务 94 ...

    gls:Goroutine本地存储

    Goroutine本地存储 该库是golang中线程本地存储模式的实现。 作为一种语言,Go掩藏了与goroutine相关的复杂性,从内部通信(通过通道)到阻止/解除阻止。 与Java不同,golang对goroutine的控制很少。 在这种情况下,...

    ajax调用java实例源码-java-curl:纯JavaCURL实现

    使用ThreadLocal解决了标准Java中cookies只能全局存储的问题,cookies对于每个线程都是隔离维护的。 线程中的cookie-store可以序列化保存,方便设置cookie池。 支持HTTPS; 支持自签名证书(JKS/BKS); 支持忽略...

    如何用Java编写一段代码引发内存泄露

    文本来自StackOverflow问答网站的一个热门讨论:如何用Java编写一段会发生内存泄露的代码。...  该类分配了大块内存(比如new byte[1000000]),在某个静态变量存储一个强引用,然后在ThreadLocal中存储它自身的引用

    java核心知识点整理.pdf

    本地方法区(线程私有) ................................................................................................................ 23 2.2.4. 堆(Heap-线程共享)-运行时数据区 ...........................

    JAVA核心知识点整理(有效)

    2.2.3. 本地方法区(线程私有) ................................................................................................................ 23 2.2.4. 堆(Heap-线程共享)-运行时数据区 .....................

    SeleniumAutomationFramework:此存储库包含Java中Selenium Framework的代码

    ThreadLocal-&gt;线程安全 范围报告5.0.5 用于测试数据维护的Excel工作表(数据提供者) 属性或Json作为配置文件 使用TestNg侦听器,例如注释转换器,ItestListener,IsuiteListener 在云或dockerizedSelenium网格...

    service_classloader

    使用的技术主要包括:classlaoder,加密解密,线程,ThreadLocal使用,反射,IO操作,jdbc, 序列化,lambda表达式,字节码工程,正则表达式等。 代码流程: 1.使用指定加密算法生成秘钥。 2.使用秘钥和加密器对字节码...

    java面经查缺补漏之第二天(坚持,加油,搞点像人的活动,嘿嘿)

    这是第二天,希望自己可以坚持住,安利一个面试复习资料 ...话不多说,上题!...(1)纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘

    Spring.3.x企业应用开发实战(完整版).part2

    Spring3.0是Spring在积蓄了3年之久后,隆重推出的一个重大升级版本,进一步加强了Spring作为Java领域第一开源平台的翘楚地位。  Spring3.0引入了众多Java开发者翘首以盼的新功能和新特性,如OXM、校验及格式化框架...

    Spring3.x企业应用开发实战(完整版) part1

    Spring3.0是Spring在积蓄了3年之久后,隆重推出的一个重大升级版本,进一步加强了Spring作为Java领域第一开源平台的翘楚地位。  Spring3.0引入了众多Java开发者翘首以盼的新功能和新特性,如OXM、校验及格式化框架...

Global site tag (gtag.js) - Google Analytics