ThreadLocal原理
八股文中技术官话连篇的概念:
它使每个线程都有一个变量的副本。(感觉有点像人机)
翻译一下:每个线程都有一个map,存储的是<一个ThreadLocal实例, 引用型变量>这样的k,v映射。
也就是让线程各自改各自的变量副本,不进行变量的共享。
下面的CAS和ThreadLocal的主要方法无关,ThreadLocal只有生成唯一的hashcode的时候用到了compareAndSet…
- Thread类中定义了一个ThreadLocal.ThreadLocalMap类型的成员变量。
// ThreadLocal values pertaining to this thread. This map is maintained
// by the ThreadLocal class.
ThreadLocal.ThreadLocalMap threadLocals = null;
- 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;
...
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); }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.hpptemplate<> 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; }
