LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

ThreadLocal底层+CAS实现

2025/8/28 Java

ThreadLocal原理

八股文中技术官话连篇的概念:
它使每个线程都有一个变量的副本。(感觉有点像人机)

翻译一下:每个线程都有一个map,存储的是<一个ThreadLocal实例, 引用型变量>这样的k,v映射。
也就是让线程各自改各自的变量副本,不进行变量的共享。
下面的CAS和ThreadLocal的主要方法无关,ThreadLocal只有生成唯一的hashcode的时候用到了compareAndSet…

  1. Thread类中定义了一个ThreadLocal.ThreadLocalMap类型的成员变量。
// ThreadLocal values pertaining to this thread. This map is maintained
// by the ThreadLocal class. 
ThreadLocal.ThreadLocalMap threadLocals = null;
  1. ThreadLocal中ThreadLocalMap的定义:key是当前ThreadLocal的实例(this),value是你要存的引用型变量。
static class ThreadLocalMap {
    // 扩展了WeakReference,意味着entry.get() == null,则不再被引用,下次gc被清除。
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
...
  1. set方法

    public void set(T value) {
     Thread t = Thread.currentThread();
     
     ThreadLocalMap map = getMap(t);
     if (map != null) {
         map.set(this, value);
     } else {
         createMap(t, value);
     }
    }
    //给当前线程的threadLocals变量赋值。
    void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
  2. ThreadLocalMap的set方法:

    private static final int HASH_INCREMENT = 0x61c88647;
    ....
    private void set(ThreadLocal<?> key, Object value) {
     Entry[] tab = table;
     int len = tab.length;
     // hashCode生成函数,计算下一个hashcode.
     // 0x61c88647是一个魔法增量
     // 0 & 15 = 0
     // 0x61c88647 & 15 = 7
     // (上一个hashcode+0x61c88647= 0xc3910c8e) & 15 = 14
     // 0x255992d5 & 15 = 5
     //...如此可以让2^n长度的hash表,更加分散的存储key,减少hash冲突
     int i = key.threadLocalHashCode & (len-1);
    
     for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
         // 由于静态内部类Entry继承了WeakReference,
         // 因此执行了Reference的get方法。
         // 返回这个引用对象所指向的那个原始对象,也就是ThreadLocal实例。
         ThreadLocal<?> k = e.get();
         if (k == key) {
             e.value = value;
             return;
         }
    
         if (k == null) {
             replaceStaleEntry(key, value, i);
             return;
         }
     }
     
     tab[i] = new Entry(key, value);
     int sz = ++size;
     if (!cleanSomeSlots(i, sz) && sz >= threshold)
         rehash();
    }
    

    CAS原理

    以java.util.concurrent.atomic.AtomicInteger为例

局部变量的地址存储在栈内存中:栈指针+偏移.,这个地址是逻辑地址。

物理地址:通过操作系统的页表转换机制,把逻辑地址转为物理地址。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
    
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            //编译的时候获取value变量的逻辑地址
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
//expect – the expected value 
// update – the new value
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

解析过程:

UNSAFE_ENTRY_SCOPED(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
  // 1. 解析JNI对象为C++的class oopDesc的对象
  oop p = JNIHandles::resolve(obj); 
  //2. 获取真实的物理地址
  volatile jint* addr = (volatile jint*)index_oop_from_field_offset_long(p, offset);
  //3.cmpxchg的具体实现各个处理器不同
  return Atomic::cmpxchg(addr, e, x) == e;
} UNSAFE_END

X86的实现:

__asm__ [volatile] ( 
    "汇编指令模板"
    : 输出操作数
    : 输入操作数  
    : 破坏描述符
);

__asm__是GCC编译器的内联汇编扩展,允许C直接嵌入汇编代码。

  • cmpxchgl: x86比较交换指令(32位)

  • %1: 输入操作数1(exchange_value)

  • (%3): 输入操作数3(dest)作为内存地址

  • =a: 输出,使用EAX寄存器

  • r: 输入,任意寄存器

  • a: 输入,使用EAX寄存器

  • “cc”: 表示会修改条件码寄存器(EFLAGS)

  • “memory”: 表示会读写内存,防止编译器优化
    src/hotspot/os_cpu/linux_x86/atomic_linux_x86.hpp

    template<>
    template<typename T>
    inline T Atomic::PlatformCmpxchg<4>::operator()(T volatile* dest,
                                                  T compare_value,
                                                  T exchange_value,
                                                  atomic_memory_order /* order */) const {
    STATIC_ASSERT(4 == sizeof(T));
    
    __asm__ volatile ("lock cmpxchgl %1,(%3)"
                      : "=a" (exchange_value)
                      : "r" (exchange_value), "a" (compare_value), "r" (dest)
                      : "cc", "memory");
    return exchange_value;
    }
    
img_show