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中,可以使用Thread类或Runnable接口来实现多线程。Thread类是一个线程的引用,而Runnable接口是一个线程的实现。 public class MyThread extends Thread { public void run() { // 多线程运行的代码 } } publi…

    多线程 2023年5月16日
    00
  • Java并发编程之ConcurrentLinkedQueue队列详情

    Java并发编程之ConcurrentLinkedQueue队列详情 什么是ConcurrentLinkedQueue ConcurrentLinkedQueue是Java中的一个并发数据结构,基于链表实现,用来维护一组元素,采用无锁算法CAS保证线程安全,被广泛应用于多线程编程场景中。 操作模式 ConcurrentLinkedQueue操作模式是先进先出…

    多线程 2023年5月17日
    00
  • C++ 线程(串行 并行 同步 异步)详解

    C++ 线程详解 C++ 中的线程是一种基于同步和异步的编程模型,可以帮助程序员更好地利用 CPU 和内存资源,提高程序性能。本篇文章将详细讲解C++ 线程的概念、分类以及用法。 线程概念 一个线程是程序执行中的单一线路,每个线程都有自己的指令计数器、栈空间和寄存器等,并同时访问共同的全局数据。C++ 中线程的作用和进程类似,一个进程包含多个线程,每个线程可…

    多线程 2023年5月16日
    00
  • Java如何实现多个线程之间共享数据

    要实现多个线程之间共享数据,Java提供了以下两种方式: 共享引用类型的数据 共享基本类型的数据 共享引用类型的数据 Java中,对象是存储在堆内存中的,每个对象都有一个地址,当多个线程对这个对象引用进行操作时,它们都指向同一个地址,因此它们访问的是同一个对象,所以可以实现数据共享。共享数据的过程中需要注意同步操作,保证线程安全。 示例1:共享对象类型的数据…

    多线程 2023年5月17日
    00
  • MySQL中SELECT+UPDATE处理并发更新问题解决方案分享

    MySQL中SELECT+UPDATE处理并发更新问题解决方案分享 在MySQL中,常常存在多个客户端同时对同一行数据进行更新的情况,这就导致了并发更新问题,会产生脏读、幻读等问题。接下来,我们将为大家分享如何通过SELECT+UPDATE来解决并发更新问题。 解决方案 MySQL提供了多种方式来解决并发更新问题,比如使用事务或者锁机制。而在本文中,我们将介…

    多线程 2023年5月17日
    00
  • Python多线程及其基本使用方法实例分析

    Python多线程及其基本使用方法实例分析 多线程的概念 多线程是“线程”这个概念的扩展,线程可以看做是一个执行流,负责程序的运行和执行,每个线程都拥有自己的一套寄存器、堆栈和局部变量等,是程序中一个独立的可执行单元。 通常情况下,一个程序运行时只有一个线程,也就是主线程,如果需要同时完成多个任务,则需要多个线程协同工作。 多线程的优点是可以一定程度上提高程…

    多线程 2023年5月16日
    00
  • java两个线程同时写一个文件

    要实现Java中两个线程同时写一个文件的话,我们可以采取以下几个步骤: 1.创建一个文件输出流对象,并将需要写入的内容转化为字节数组。 2.将文件输出流对象以可追加的方式打开。 3.在需要写入的线程中,将字节数组写入到文件中。 4.在写入文件的过程中,需要使用synchronized关键字来保证线程同步,避免写入冲突的问题。 5.实现完整的示例代码,演示多线…

    多线程 2023年5月17日
    00
  • 详解java解决分布式环境中高并发环境下数据插入重复问题

    详解 Java 解决分布式环境中高并发环境下数据插入重复问题 背景 在高并发环境下,数据插入操作很容易出现重复插入的问题。例如,多个用户同时提交一个相同的表单,系统可能会将同样的数据插入数据库中多次,造成数据不一致的问题。这种情况在分布式环境下尤其常见,因为不同节点的时间戳可能不一致。 解决方案 方法一:利用 Unique 约束 在数据库中设置 Unique…

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