Java并发编程学习之ThreadLocal源码详析

首先我们需要了解什么是ThreadLocal。ThreadLocal是一个与线程相关的类,它提供了线程本地存储(ThreadLocal Storage)功能,也就是说,对于同一个ThreadLocal实例,每个线程都可以获取相同但是独立的值。这样,多个线程之间可以相互独立,不会互相冲突,实现了数据的隔离。

一、ThreadLocal如何实现线程本地存储的
在讲ThreadLocal的源码之前,我们先看看ThreadLocal如何实现线程本地存储的。ThreadLocal内部维护了一个Map,这个Map的key是Thread,value是该线程的本地变量值。当我们调用ThreadLocal.get()方法时,ThreadLocal首先获取当前线程,然后将当前线程作为key去Map中查找对应的value,如果存在,直接返回该value;否则,调用ThreadLocal.initialValue()方法来创建一个初始的本地变量值,并将这个变量与当前线程关联起来。这个关联使用了WeakReference来实现,而WeakReference可以有效防止内存泄露。
ThreadLocal.set()方法实际上是调用了ThreadLocalMap.set(ThreadLocal, Object)方法来实现的。ThreadLocalMap是ThreadLocal的内部类,它继承了WeakReference,并且重写了finalize()方法,用来删除这个键值对。
ThreadLocalMap的put()方法是完全的线程不安全的,因为可能出现多个线程同时进行线性探测,导致数据覆盖。所以,ThreadLocalMap在实现时使用了“开放地址法”(open addressing)来解决hash冲突。

二、ThreadLocal源码详解
1、ThreadLocal的属性和构造函数

public class ThreadLocal<T> {

    /**
     * The next hash code to be given out. Updated atomically. Start with
     * a random value to avoid collisions with default hash codes of 0.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /**
     * The difference between successively generated hash codes, approximately
     *  golden ratio multiplied by 2^32.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Creates a new thread-local variable.
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread's copy of this thread-local
     * variable. If the variable has no value for the current thread, it is
     * first initialized to the value returned by an invocation of the
     * {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        ...
    }

    /**
     * Sets the current thread's copy of this thread-local variable to the
     * specified value. Most subclasses will have no need to override this
     * method, relying solely on the {@link #initialValue} method to set the
     * values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        ...
    }

    /**
     * Removes the current thread's value for this thread-local variable.
     */
    public void remove() {
        ..
    }

    /**
     * Computes the initial value for this thread-local variable. This
     * method is called once per accessing thread for each thread-local,
     * the first time each thread accesses the variable with {@link #get}
     * or {@link #set}.
     *
     * <p>This implementation simply returns <tt>null</tt>; if the
     * programmer desires thread-local variables to have an initial value
     * other than <tt>null</tt>, <tt>ThreadLocal</tt> must be subclassed,
     * and this method overridden. Typically, an anonymous inner class
     * will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

}

ThreadLocal主要需要了解的是它的两个方法,一个是get()方法,一个是set()方法。这两个方法是ThreadLocal的核心功能,也是我们平常使用最多的两个方法。然后我们看到ThreadLocal内部有一个threadLocalHashCode属性,它是用来做什么的呢?这个属性是为了应对多个ThreadLocal变量在一个线程中使用时产生的冲突问题的,每一个ThreadLocal对象都会有自己的唯一的threadLocalHashCode来表示。再看到静态变量nextHashCode,这个变量是用来给threadLocalHashCode赋值的,每次访问ThreadLocal都会给threadLocalHashCode赋值。需要说明的是,nextHashCode是线程安全的,使用了AtomicInteger来实现。

2、ThreadLocal的get()方法

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();  // 没有值就调用ThreadLocal的初始值方法,该方法默认返回null
}

ThreadLocal的get()方法首先获取当前线程,然后通过当前线程获取到它对应的ThreadLocalMap,如果Map存在并且查找到了当前ThreadLocal的Entry,则直接返回该Entry中存储的值。如果Map不存在,则调用setInitialValue()方法来设置默认值,即调用ThreadLocal的initialValue()方法。

3、ThreadLocal的set()方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);  // 调用ThreadLocalMap的set方法将ThreadLocal关联到当前线程的Map中
    else
        createMap(t, value);  // 如果Map不存在就直接创建ThreadLocalMap并保存值
}

ThreadLocal的set()方法首先获取当前线程,然后通过当前线程获取到它对应的ThreadLocalMap,如果Map存在,则在Map中设置当前ThreadLocal对应的值;如果Map不存在,则创建一个Map,并在其中设置当前ThreadLocal对应的值。其中,ThreadLocalMap的set()方法是完全的线程不安全的,因为可能出现多个线程同时进行线性探测,导致数据覆盖。所以,ThreadLocalMap在实现时使用了“开放地址法”(open addressing)来解决hash冲突。

4、ThreadLocal示例1

public class ThreadLocalTest1 {
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            int myCount = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " : " + myCount);
            threadLocal.set(myCount + 1);
            myCount = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " : " + myCount);
            threadLocal.set(myCount + 1);
            myCount = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " : " + myCount);
            threadLocal.remove(); // 删除 threadLocal
        }
    }

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
}

这个例子创建了一个ThreadLocal变量和一个Runnable线程。我们通过Runnable中的get()方法获取ThreadLocal的值,然后将它打印出来;接着,通过set()方法给它赋新值,并再次打印出来。使用remove()方法可以删除这个ThreadLocal变量。

5、ThreadLocal示例2

public class ThreadSafeFormatter {
    public static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static String format(Date date) {
        return DATE_FORMAT.get().format(date);
    }
}

这是一个线程安全的日期格式化类,使用了ThreadLocal变量。通过ThreadLocal.withInitial方法来创建ThreadLocal变量,这个方法会返回一个Supplier对象,然后我们调用get()方法就可以获取到一个SimpleDateFormat对象。然后,在format()方法中使用ThreadLocal.get()方法获取SimpleDateFormat对象来进行日期格式化。

以上就是Java并发编程学习之ThreadLocal源码详析的完整攻略,希望能够对你有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程学习之ThreadLocal源码详析 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • 阿里常用Java并发编程面试试题总结

    阿里常用Java并发编程面试试题总结是一份非常全面且重要的Java并发编程面试试题汇总,下面是一个完整的攻略: 1. 理解Java内存模型 Java内存模型是Java中并发编程的关键。在Java内存模型中,每个线程都会有自己的本地工作内存,同时所有线程都可以访问共享内存,这个共享内存指的是主内存。Java内存模型的主要作用是规定了线程如何与主内存交互,以及线…

    多线程 2023年5月16日
    00
  • java并发学习之BlockingQueue实现生产者消费者详解

    Java并发学习之BlockingQueue实现生产者消费者详解 在Java中,为了支持并发编程,提供了许多能够协调多线程之间互相工作的机制。其中之一就是BlockingQueue,它提供了一个线程安全的队列,支持多线程并发处理。 本攻略将详细讲解BlockingQueue的实现以及在生产者消费者模型中的应用。 BlockingQueue的定义和使用 Blo…

    多线程 2023年5月17日
    00
  • Java多线程深入理解

    Java多线程深入理解攻略 在进行深入理解Java多线程的过程中,需要掌握以下几点: 1. 线程的创建和启动 Java中线程的创建有两种方式,一种是继承Thread类,一种是实现Runnable接口。其中,实现Runnable接口的方式更加灵活,因为一个类可以实现多个接口。 // 继承Thread类 class MyThread extends Thread…

    多线程 2023年5月16日
    00
  • 深入理解 Java、Kotlin、Go 的线程和协程

    深入理解 Java、Kotlin、Go 的线程和协程攻略 前言 线程和协程是现代编程中最为重要的并发编程方式之一。Java、Kotlin 和 Go 都是常用的编程语言。本文将围绕这几门语言的线程和协程进行分析和比较,助您深入理解它们的本质和局限。 线程和协程的基本概念 线程 线程是操作系统中独立的执行单元。多线程可以提高程序的效率,使程序可以同时完成多个任务…

    多线程 2023年5月17日
    00
  • linux下的C\C++多进程多线程编程实例详解

    Linux下的C/C++多进程多线程编程实例详解 本文将为读者讲解Linux下的C/C++多进程多线程编程实例,并提供两个示例说明。Linux下的多进程多线程编程是一个方便且高效的编程方式,可以有效地提高程序的并发性和性能,是实现高并发、高性能的重要编程方式。 多进程编程实例 多进程编程是一种并发编程的模式,可以有效地提高程序的并发性。在Linux下,多进程…

    多线程 2023年5月17日
    00
  • .net中线程同步的典型场景和问题剖析

    针对“.net中线程同步的典型场景和问题剖析”的话题,我来进行详细讲解,包括以下几个部分: 线程同步的概念 线程同步的必要性和作用 线程同步的实现方式 .net中线程同步的典型场景和问题剖析 示例说明 1. 线程同步的概念 线程同步是指在多个线程之间,对共享资源的访问进行协调和管理,以避免竞争条件和死锁等问题。 2. 线程同步的必要性和作用 当多个线程同时访…

    多线程 2023年5月16日
    00
  • java之使用多线程代替for循环(解决主线程提前结束问题)

    下面是使用多线程代替for循环的攻略,我将分几个部分进行讲解。 什么是多线程? 多线程是指同时执行多个线程(程序),也就是并发执行。与单线程相比,多线程可以将程序的性能提高数倍,但是多线程也存在一些问题,如线程安全、线程同步等。 为什么要使用多线程代替for循环? 在Java中,使用for循环进行数据的处理是非常常见的操作。但是当待处理的数据量较大时,使用f…

    多线程 2023年5月17日
    00
  • SpringBoot多线程进行异步请求的处理方式

    让我们来详细讲解一下Spring Boot多线程进行异步请求处理的完整攻略。 什么是异步请求 异步请求是指客户端通过发送请求到服务器端,在等待服务器响应的过程中不会阻塞当前进程的执行,同时也不会阻塞其他的程序执行或用户操作。 相比于传统的同步请求,异步请求的主要优点是提高了应用程序的性能和并行处理能力。 Spring Boot多线程处理异步请求的方式 在Sp…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部