Java并发编程系列之六:深入理解ThreadLocal

简介: 1、多个线程去获取一个共享变量时,要求获取的是这个变量的初始值的副本。每个线程存储这个变量的副本,对这个变量副本的改变不会影响变量本身。适用于多个线程依赖不同变量值完成操作的场景。比如:多数据源的切换spring声明式事务2、将ThreadLocal设置成private static的,这样ThreadLocal会尽量和线程本身一起回收。

引言

无论实际项目实战还是面试,ThreadLocal都是一个绕不开的话题,本文主要从源码角度和大家一起探讨下ThreadLocal的神秘面纱。

  • ThreadLocal是什么?它能干什么?
  • ThreadLocal源码分析
  • 总结


一、ThreadLocal是什么?它能干什么?

ThreadLocal 是一个线程的本地变量, 也就意味着这个变量是线程独有的,是不能与其他线程共享的,它并不是解决多线程共享变量的问题。

所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。


ThreadLocal的思想就是用空间换时间,使各线程都能访问属于自己这一份的变量副本,变量值不互相干扰,减少同一个线程内的多个函数或者组件之间一些公共变量传递的复杂度。


image.png

二、ThreadLocal源码分析

1、ThreadLocalMap解析

ThreadLocal内部定义了一个ThreadLocalMap的内部类,ThreadLocalMap实际利用Entry来实现key-value的存储,如下所示:

static class ThreadLocalMap {
  static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
}

ThreadLocalMap是实现线程隔离机制的关键,从以上代码可以看出Entry的key就是ThreadLocal,而value就是值。同时,Entry也继承WeakReference,所以说Entry所对应key(ThreadLocal实例)的引用为一个弱引用。


我们主要来看下核心的getEntry()、set(ThreadLocal> key, Object value)方法

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
 private void set(ThreadLocal<?> key, Object value) {
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            Entry[] tab = table;
            int len = tab.length;
            // 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
            int i = key.threadLocalHashCode & (len-1);
      // 采用“线性探测法”,寻找合适位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
        // 若key 存在,直接覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
        // key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
                if (k == null) {
                  // 用新元素替换陈旧的元素
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
      //创建新元素
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

2、核心方法解析

(1) get()

返回此线程局部变量的当前线程副本中的值

public T get() {
    //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量 threadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
          // 从当前线程的ThreadLocalMap获取相对应的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取目标值
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先通过当前线程获取所对应的成员变量ThreadLocalMap,然后通过ThreadLocalMap获取当前ThreadLocal的Entry,最后通过所获取的Entry获取目标值result。


(2) set(T value)

将此线程局部变量的当前线程副本中的值设置为指定值。

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,如果不为空,则调用ThreadLocalMapset()方法,key就是当前ThreadLocal,如果不存在,则调用createMap()方法新建一个。

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

(3) initialValue()

返回此线程局部变量的当前线程的初始值。

protected T initialValue() {
        return null;
    }

该方法定义为protected级别且返回为null,需要其子类实现其功能,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。


(4) remove()

移除此线程局部变量当前线程的值。

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

三、总结

1、多个线程去获取一个共享变量时,要求获取的是这个变量的初始值的副本。每个线程存储这个变量的副本,对这个变量副本的改变不会影响变量本身。适用于多个线程依赖不同变量值完成操作的场景。比如:

  • 多数据源的切换
  • spring声明式事务

2、将ThreadLocal设置成private static的,这样ThreadLocal会尽量和线程本身一起回收。


相关文章
|
1天前
|
存储 安全 Java
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现
|
1天前
|
缓存 Java 程序员
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
|
1天前
|
缓存 Java 数据库
Java并发编程中的锁优化策略
【5月更文挑战第9天】 在高负载的多线程应用中,Java并发编程的高效性至关重要。本文将探讨几种常见的锁优化技术,旨在提高Java应用程序在并发环境下的性能。我们将从基本的synchronized关键字开始,逐步深入到更高效的Lock接口实现,以及Java 6引入的java.util.concurrent包中的高级工具类。文中还会介绍读写锁(ReadWriteLock)的概念和实现原理,并通过对比分析各自的优势和适用场景,为开发者提供实用的锁优化策略。
2 0
|
1天前
|
算法 安全 Java
深入探索Java中的并发编程:CAS机制的原理与应用
总之,CAS机制是一种用于并发编程的原子操作,它通过比较内存中的值和预期值来实现多线程下的数据同步和互斥,从而提供了高效的并发控制。它在Java中被广泛应用于实现线程安全的数据结构和算法。
13 0
|
2天前
|
JavaScript 小程序 Java
基于java的少儿编程网上报名系统
基于java的少儿编程网上报名系统
10 2
|
2天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
10 0
|
2天前
|
Java 测试技术 图形学
掌握Java GUI编程基础知识
掌握Java GUI编程基础知识
6 0
|
2天前
|
SQL Java 数据库连接
Java数据库编程实践:连接与操作数据库
Java数据库编程实践:连接与操作数据库
8 0
|
2天前
|
安全 Java 程序员
深入探索Java泛型编程
深入探索Java泛型编程
6 0
|
2天前
|
Java 编译器 开发者
Java并发编程中的锁优化策略
【5月更文挑战第8天】在Java并发编程中,锁是实现线程同步的关键机制。为了提高程序的性能,我们需要对锁进行优化。本文将介绍Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级和读写锁等方法,以帮助开发者提高多线程应用的性能。