`
bolinyang
  • 浏览: 75340 次
  • 性别: 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线程编程中ThreadLocal类的使用共

    Java线程编程中的ThreadLocal类是一个非常重要的工具,它在多线程环境下提供了一种线程局部变量的机制。ThreadLocal并非是简单的变量,而是一种能够确保每个线程都拥有独立副本的变量容器。理解ThreadLocal的工作...

    Java多线程 之 临界区、ThreadLocal.docx

    Java多线程编程中,临界区和ThreadLocal是两种重要的并发控制机制,它们用于解决多线程环境下的数据安全问题。 1. **临界区(Critical Section)** 临界区是指一段代码,它在同一时刻只允许一个线程进行访问。在...

    java线程本地变量ThreadLocal详解

    Java 线程本地变量 ThreadLocal 详解 ThreadLocal 是 Java 中的一个类,提供了线程安全的对象封装,用于解决多线程访问数据的冲突问题。ThreadLocal 的主要目的是为每个线程提供一个变量副本,从而隔离了多个线程...

    java 简单的ThreadLocal示例

    Java中的ThreadLocal是一个非常重要的工具类,它在多线程编程中扮演着独特角色,尤其在处理线程间数据隔离和共享时。ThreadLocal不是线程本身,而是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地改变...

    java事务 - threadlocal

    当Java事务与ThreadLocal结合使用时,可以在不同的线程中维护各自的事务状态,比如在Spring框架中,每个线程的ThreadLocal可以存储一个TransactionStatus对象,这样就可以在线程内部管理当前事务的状态,而不会影响...

    电子书《java线程》

    10. **线程本地存储(ThreadLocal)**:为每个线程提供独立的变量副本,避免了线程间的共享数据冲突。 11. **并发集合**:如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合类在并发环境下提供了高性能的读写...

    ThreadLocal 线程本地变量 及 源码分析.rar_开发_设计

    ThreadLocal是Java编程语言中的一个类,用于在多线程环境下提供线程局部变量。它为每个线程创建了一个独立的变量副本,每个线程只能访问自己的副本,不会影响其他线程。这种机制有助于实现线程安全,尤其在需要线程...

    java中ThreadLocal详解

    在Java多线程编程中,`ThreadLocal`是一个非常重要的工具类,它提供了一种在每个线程内部存储线程私有实例的方法。通常情况下,当多个线程共享某个变量时,可能会引发线程安全问题。而`ThreadLocal`则提供了另一种...

    Java中ThreadLocal的设计与使用

    Java中的ThreadLocal是一个非常重要的工具类,它在多线程编程中扮演着独特角色,用于为每个线程提供独立的变量副本。理解ThreadLocal的工作原理和使用方法对于编写高效、安全的多线程程序至关重要。 ### ...

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

    从本质上说,ThreadLocal是一种存储机制,它可以在每个线程中存储一个变量的副本,这样每个线程都可以访问自己的变量副本,而不需要与其他线程共享同一个变量。这种机制可以解决多线程编程中的线程安全问题,并且...

    Java并发编实践之ThreadLocal变量.doc

    以下是一个简单的ThreadLocal使用示例,创建了一个`SequenceNumber`类,其中使用ThreadLocal存储每个线程独立的序列号。每个线程都有自己的序列号计数器,互不干扰: ```java public class SequenceNumber { ...

    ThreadLocal应用示例及理解

    **线程局部变量(ThreadLocal)是Java编程中一个非常重要的工具类,它在多线程环境下提供了线程安全的数据存储。ThreadLocal并不是一个变量,而是一个类,它为每个线程都创建了一个独立的变量副本,使得每个线程都...

    java多线程编程总结

    线程本地存储,如 `ThreadLocal` 类,提供了线程安全的数据结构,使得每个线程拥有自己的副本,避免了数据竞争问题。 #### 四、Java线程:线程状态的转换 - **线程的状态** Java线程的状态包括新建 (`NEW`)、...

    java中ThreadLocal类的使用

    Java中的`ThreadLocal`类是一个非常实用的工具,它提供了线程局部变量的功能。线程局部变量意味着每个线程都拥有自己独立的变量副本,互不干扰,这在多线程编程中尤其有用,可以避免数据共享带来的同步问题。下面...

    Java资料-详解ThreadLocal

    `ThreadLocal`的工作原理是为每个线程创建一个单独的实例副本,这些副本存储在一个与线程相关的映射表中。当线程执行时,它首先查找自己的副本,如果没有,则会自动创建。 让我们通过一个简单的例子来理解`...

    ThreadLocal_ThreadLocal源码分析_

    在ThreadLocal中,每个实例都有一个内部Map,这个Map存储了键值对,键是ThreadLocal实例,值就是线程本地的变量副本。在JDK 8之前,这个内部Map是`ThreadLocal.ThreadLocalMap`,而从JDK 8开始,改为了`...

    java 中ThreadLocal实例分析

    Java 中的 ThreadLocal 实例分析是指在多线程环境下,如何使用 ThreadLocal 来实现线程安全。ThreadLocal 是 Java 中的一种机制,用于在多个线程中实现变量的隔离。 在上面的代码中,我们可以看到,ConsumeClientA ...

    Java多线程文档

    4. 使用线程局部变量(ThreadLocal)存储线程私有数据。 这篇文档通过深入浅出的方式讲解了Java多线程编程的核心概念、常用工具和最佳实践,对于理解和应用Java多线程有着极大的帮助。无论是初学者还是有经验的...

    java线程学习笔记

    线程本地存储(ThreadLocal)提供了线程内部的局部变量,这些变量对其他线程而言是隔离的。在多线程环境下,ThreadLocal可以用来存储线程特有对象,避免使用全局变量,从而提供线程安全。 线程阻塞是指线程在某些...

    ThreadLocal

    在Java中,ThreadLocal的工作原理是为每个线程创建一个单独的存储空间,每个线程可以独立地读写这个变量,而不会影响其他线程。当我们创建一个新的ThreadLocal实例时,它并不会立即分配内存,而是等到线程第一次调用...

Global site tag (gtag.js) - Google Analytics