引言

本文介绍了JDK中罕用的并发库(JUC)的应用形式,并且自上而下地分析了其实现原理,从间接上级框架AbstractQueuedSynchronizer,也就是大家常说的AQS,再到其中应用的CAS,Wait,Park,最初到操作系统层面的Mutex,Condition,心愿通过这篇文章,大家可能对整个Java并发有一个清晰全面的意识,而且把这些内容串在一起你会发现他们实质上都是相通的。

JUC

java.util.concurrent包(JUC)中,有各式各样的并发管制工具,这里咱们简略介绍一个罕用的工具及其应用形式。

Atomic类

Atomic 类有很多种,它们都在 java.util.concurrent.atomic 包中。根本都是通过 CAS(CompareAndSwap)来实现的,而 CAS 的具体实现依赖于体系结构提供的指令。

这里咱们仅介绍几个例子,并不会介绍每一个Atomic类的应用。首先看一下AtomicInteger,通过它咱们能够无锁化的批改一个int类型的值,并且可能保障批改过程是原子的。

public static class LockTest {    private AtomicInteger sum = new AtomicInteger(0);    public void increase() {        sum.incrementAndGet();    }}

比方统计一个网页的访问量时,就能够应用它,因为不会应用到锁,所以没有上下文切换的耗费,速度很快。

如果你不仅仅是批改一个根底类型的数据,例如一次要批改好几个根底数据类型得话,你能够把它们封装到一个对象中,而后应用AtomicReference来进行整个对象的更新操作。下例中,咱们就一次性更新了一个对象的所有属性。

public static class LockTest {    private AtomicReference<LockTest> reference = new AtomicReference<>();    private int test1;    private int test2;    public void changeObject() {        reference.getAndUpdate(new UnaryOperator<LockTest>() {            @Override public LockTest apply(LockTest test) {                LockTest newItem = new LockTest();                newItem.test1 = test.test1 + 1;                newItem.test2 = test.test2 - 1;                return newItem;            }        });    }}

Aomic类尽管很快,然而也有一个问题就是ABA问题,当一个Atomic的值从A批改为B,再从新批改为A时,尽管值扭转了,然而在进行CAS时,会错认为该值没有发生变化,为了解决这类问题,你能够应用AtomicStampedReference。它通过一个版本号来控制数据的变动,如果遵循应用标准,即每次进行批改时都将版本号加一,那么就能够杜绝ABA问题。

public static class LockTest {    private AtomicStampedReference<LockTest> reference = new AtomicStampedReference<>(null, 0);    private int test1;    public void changeObject() {        LockTest newObject = new LockTest();        for (; ; ) {            int previousStamp = reference.getStamp();            LockTest previousObject = reference.getReference();            if (reference.compareAndSet(previousObject, newObject, previousStamp, previousStamp + 1)) {                break;            }        }    }}

Semaphore

Semaphore(信号量)和synchronized相似,是控制线程是否进入某一同步代码区的一种伎俩,然而synchronized每次只有一个过程能够进入同步代码区,而Semaphore能够指定多个线程同时拜访某个资源。

public static class LockTest {    public static void main(String[] args) {        ExecutorService threadPool = Executors.newFixedThreadPool(300);        Semaphore semaphore = new Semaphore(5);        for (int i = 0; i < 100; i++) {            int finali = i;            threadPool.execute(() -> {                try {                    semaphore.acquire();                    System.out.println("Index:" + finali);                    Thread.sleep(2000);                    semaphore.release();                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }        threadPool.shutdown();    }}

上例中,咱们设定应用了5个许可证(同一时刻最多5个线程进入同步区),每次调用acquire都会耗费一个许可证,调用release时开释一个许可证,当许可证有余时调用acquire就会进入队列期待,值得一提的是Semaphore蕴含两种模式,偏心模式和非偏心模式,在偏心模式下,获取许可证是以FIFO的程序进行,而在非偏心模式,是不能保障程序的。

CountDownLatch

CountDownLatch是一个同步工具类,它容许一个或多个线程始终期待,直到其余线程的操作执行完再执行。上面看一个例子:

public static class LockTest {    public static void main(String[] args) throws InterruptedException {        ExecutorService threadPool = Executors.newFixedThreadPool(1);        CountDownLatch countDownLatch = new CountDownLatch(5);        for (int i = 0; i < 5; i++) {            int finali = i;            threadPool.submit(() -> {                try {                    System.out.println("Index:" + finali);                    Thread.sleep(2000);                    countDownLatch.countDown();                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }        countDownLatch.await();        System.out.println("Finish");    }}

首先,咱们指定CountDownLatch期待5个线程实现工作,在每个线程执行完工作之后,都调用countDown函数,它会将CountDownLatch外部的计数器减1,当计数器为0时,CountDownLatch::await函数才会返回。我个别用它来实现Future接口。值得一提的是,CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch应用结束后,就不能再次被应用。

CyclicBarrier

CyclicBarrier和CountDownLatch十分类似,它也能够实现线程间的计数期待,然而它的性能比CountDownLatch更加简单和弱小。它能够管制一组线程全副实现第一轮工作时,再同时开始让它们执行下一轮工作。

public static class LockTest {    public static void main(String[] args) {        ExecutorService threadPool = Executors.newFixedThreadPool(5);        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("barrierAction merge data"));        for (int i = 0; i < 5; i++) {            int finali = i;            threadPool.submit(() -> {                try {                    System.out.println("Task 1 Begin Index:" + finali);                    Thread.sleep(ThreadLocalRandom.current().nextInt(2000));                    System.out.println("Task 1 Finished Index:" + finali);                    cyclicBarrier.await();                    System.out.println("Task 2 Begin Index:" + finali);                    Thread.sleep(ThreadLocalRandom.current().nextInt(2000));                } catch (InterruptedException | BrokenBarrierException e) {                    e.printStackTrace();                }            });        }    }}

CyclicBarrier很适宜进行数据分组解决的工作,而且下一轮工作依赖上一轮工作的后果,比方咱们将一个大工作拆分成很多小工作,当所有小工作实现时,咱们能够通过barrierAction合并上一轮工作的后果,而后再开始下一轮工作。

对于CyclicBarrier和CountDownLatch的区别:
CountDownLatch:A synchronized aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch:一个或者多个线程,期待其余多个线程实现某件事情之后能力执行)
CyclicBarrier:A synchronized aid that allows aset of threads to all wait for each other to reach a common barrier point.(CyclicBarrier:多个线程互相期待,直到达到同一个同步点,再持续一起执行。)

ThreadLocal

通常状况下,咱们创立的变量是能够被任何一个线程拜访并批改的。然而JDK也为咱们提供了让某一变量独享与各个线程的计划,也就是ThreadLocal。因为每个线程都有本人专属的变量,所以各个线程在操作ThreadLocal变量时不须要加锁。

public static class LockTest {    public static void main(String[] args) {        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();        new Thread(new Runnable() {            @Override public void run() {                try {                    System.out.println("Thread 1 Current Value:" + threadLocal.get());                    threadLocal.set(10);                    Thread.sleep(500);                    System.out.println("Thread 1 Current Value:" + threadLocal.get());                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }).start();        Thread.sleep(100);        new Thread(new Runnable() {            @Override public void run() {                try {                    System.out.println("Thread 2 Current Value:" + threadLocal.get());                    threadLocal.set(5);                    Thread.sleep(1000);                    System.out.println("Thread 2 Current Value:" + threadLocal.get());                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }).start();    }}

threadLocal 的初始值是 null,而后线程1先启动将值改为 10,100 ms 后线程2启动,会发现 threadLocal 的值依然为 null,而后将其改为 5,400ms 后线程1从睡眠中昏迷,发现 threadLocal 的值依然为 10,可见这两个线程所观测到的 threadLocal 值是各自独立的。

ThreadLocal实现

这里咱们简略地介绍一下 ThreadLocal 的实现原理,在每个 Thread 对象中都保留了一个 ThreadLocal Map,其中 key 为 ThreadLocal 对象,Value 为 ThreadLocal 的值。这里 ThreadLocalMap 中的 Entry 应用了弱援用,是为了帮忙 GC。当一个 ThreadLocal 对象不再被援用时,就会被 GC,这时候 ThreadLocalMap 中就会呈现 key 为 null 的状况。然而因为 value 是强援用,所以如果 key 为 null 的数据不加治理的话,就会呈现内存透露问题。ThreadLocalMap实现中曾经思考了这种状况,在调用 set()、get()、remove() 办法的时候,会清理掉 key 为 null 的记录。应用完 ThreadLocal办法后最好手动调用remove()办法,来帮忙 GC。

public class Thread implements Runnable {    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;}static class ThreadLocalMap {    /**     * The entries in this hash map extend WeakReference, using     * its main ref field as the key (which is always a     * ThreadLocal object).  Note that null keys (i.e. entry.get()     * == null) mean that the key is no longer referenced, so the     * entry can be expunged from table.  Such entries are referred to     * as "stale entries" in the code that follows.     */    static class Entry extends WeakReference<ThreadLocal<?>> {        /** The value associated with this ThreadLocal. */        Object value;        Entry(ThreadLocal<?> k, Object v) {            super(k);            value = v;        }    }}
如果一个对象只具备弱援用,那么下次GC时该对象就会被清理。弱援用与软援用的区别在于:只含有软援用的对象只有在内存不足(行将产生OOM)时才会革除,而只含有弱援用的对象下次GC时就会革除无论内存是否缓和。

当咱们想要获取 ThreadLocal 值的时候,会从以后 Thread 的 ThreadLocalMap 中查找,如果没有找到时,它会将初始值塞入该 Map 并返回。

/** * 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) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */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;    // 计算 hash 值对应的槽位    int i = key.threadLocalHashCode & (len-1);    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        ThreadLocal<?> k = e.get();        // 如果该槽位存储的 ThreadLocal 对象就是本人,就返回        if (k == key) {            e.value = value;            return;        }        // 如果遍历找到了一个空的槽位,就占用它        if (k == null) {            replaceStaleEntry(key, value, i);            return;        }    }    tab[i] = new Entry(key, value);    int sz = ++size;    // 革除 key 为 null的槽位,如果size过大就扩容,扩容阈值是哈希表长度的 2 / 3    if (!cleanSomeSlots(i, sz) && sz >= threshold)        rehash();}

ThreadLocalMap外部应用一个数组来保留数据,相似HashMap;每个ThreadLocal在初始化的时候会调配一个threadLocalHashCode,而后和数组的长度进行取模来计算以后 ThreadLocal 变量所处的槽位。然而这样也会呈现hash抵触的状况,在HashMap中解决抵触是应用链表+红黑树的形式。而在ThreadLocalMap中,咱们能够看到它间接应用nextIndex,进行遍历操作,期间如果找到了本人之前应用到的槽位,就间接返回,否则占用一个没有被应用的槽位。很显著当 ThreadLocal 很多时这样效率很低。获取 ThreadLocal 值的过程也相似,先通过 hash 找到槽位,如果该槽位保留的不是咱们要的 ThreadLocal 对象,则进行遍历查找。

private Entry getEntry(ThreadLocal<?> key) {    int i = key.threadLocalHashCode & (table.length - 1);    Entry e = table[i];    // 间接 hash 槽位找到了指标对象,间接返回    if (e != null && e.get() == key)        return e;    else        // 否则,遍历查找        return getEntryAfterMiss(key, i, e);}private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {    Entry[] tab = table;    int len = tab.length;    while (e != null) {        ThreadLocal<?> k = e.get();        if (k == key)            return e;        if (k == null)            // 这里如果发现了空的槽位,要进行从新 hash,来晋升效率            expungeStaleEntry(i);        else            i = nextIndex(i, len);        e = tab[i];    }    return null;}private int expungeStaleEntry(int staleSlot) {    Entry[] tab = table;    int len = tab.length;    // expunge entry at staleSlot    tab[staleSlot].value = null;    tab[staleSlot] = null;    size--;    // Rehash until we encounter null    Entry e;    int i;    for (i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {        ThreadLocal<?> k = e.get();        // 同样如果找到了 key 为 null 的槽位,就把他清空来帮忙 GC        if (k == null) {            e.value = null;            tab[i] = null;            size--;        } else {            // 如果一个节点通过hash 计算的槽位,和理论保留的槽位不一样时,从计算所得的槽位登程,找到一个为 null 的槽位,并将该节点存进去            int h = k.threadLocalHashCode & (len - 1);            if (h != i) {                tab[i] = null;                // Unlike Knuth 6.4 Algorithm R, we must scan until                // null because multiple entries could have been stale.                while (tab[h] != null)                    h = nextIndex(h, len);                tab[h] = e;            }        }    }    return i;}

fastThreadLocal

正是因为 JDK 提供的 ThreadLocal 存在性能问题,所以在 Netty 中,对 ThreadLocal 进行了改写,Netty 配套的提供了 FastThreadLocalFastThreadLocalThreadFastThreadLocalRunnable ,其中 FastThreadLocalRunnable 比较简单,就是在原始 Runnable 接口之上的装璜者。达到了执行结束后主动革除 FastThreadLocal 的成果。

final class FastThreadLocalRunnable implements Runnable {    private final Runnable runnable;    private FastThreadLocalRunnable(Runnable runnable) {        this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");    }    @Override    public void run() {        try {            runnable.run();        } finally {            FastThreadLocal.removeAll();        }    }    static Runnable wrap(Runnable runnable) {        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);    }}

FastThreadLocal 必须配合 FastThreadLocalThread 一起应用,能力达到性能更优的成果,否则可能还不如间接应用 JDK 本身的 ThreadLocal。FastThreadLocal 为什么能比 JDK 的实现更快呢,起因就在于 FastThreadLocal 不是应用 hash 表来保留 ThreadLocal 的值,而是间接应用了数组。让咱们看看它的具体实现计划吧。FastThreadLocal 所有的数据都保留在了 InternalThreadLocalMap 中, 这里咱们先明确它是一个保留数据的容器就行,它是如何保留数据的咱们前面介绍。那么,InternalThreadLocalMap 这个容器存在哪了呢?是不是像 JDK 提供的 ThreadLocal 一样把容器(JDK 中用到的Hash 表)存在了Thread 对象中呢?没错,如果咱们应用的是 FastThreadLocalThread 的话,InternalThreadLocalMap 就是 FastThreadLocalThread 的一个成员变量。

public class FastThreadLocalThread extends Thread {    // This will be set to true if we have a chance to wrap the Runnable.    private final boolean cleanupFastThreadLocals;    // 保留了所有的 FastThreadLocal 值    private InternalThreadLocalMap threadLocalMap;    // ...}

上面给大家展现的是 UnpaddedInternalThreadLocalMap 它是 InternalThreadLocalMap 的父类,大部分重要的数据都是保留在 UnpaddedInternalThreadLocalMap 中的。

class UnpaddedInternalThreadLocalMap {    // 当没有应用 FastThreadLocalThread 时,通过 JDK ThreadLocal 来保留    static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();    static final AtomicInteger nextIndex = new AtomicInteger();    /** Used by {@link FastThreadLocal} */    Object[] indexedVariables;    // ...}

在 UnpaddedInternalThreadLocalMap 中,通过 JDK 提供的 ThreadLocal 来保留一个 InternalThreadLocalMap,以应答没有应用 FastThreadLocalThread 的状况。正因如此,当 FastThreadLocal 须要获取 InternalThreadLocalMap 对象时会依据以后运行的线程是不是 FastThreadLocalThread 来决定到底从哪里提取 InternalThreadLocalMap。

// InternalThreadLocalMap.javapublic static InternalThreadLocalMap get() {    Thread thread = Thread.currentThread();    if (thread instanceof FastThreadLocalThread) {        // 以后应用的是 FastThreadLocalThread        return fastGet((FastThreadLocalThread) thread);    } else {        // 以后应用的是 Thread        return slowGet();    }}// 应用 FastThreadLocalThread 时,间接从成员变量获取private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {    InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();    if (threadLocalMap == null) {        thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());    }    return threadLocalMap;}// 应用 Thread 时,通过 InternalThreadLocalMap 外部的 ThreadLocal 获取private static InternalThreadLocalMap slowGet() {    ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;    InternalThreadLocalMap ret = slowThreadLocalMap.get();    if (ret == null) {        ret = new InternalThreadLocalMap();        slowThreadLocalMap.set(ret);    }    return ret;}

明确了 InternalThreadLocalMap 这个容器存在哪了之后,咱们再来看看这个容器外部是如何保留数据的。再回到 UnpaddedInternalThreadLocalMap 的代码中。咱们能够看到这里有一个 indexedVariables 数组,它就是保留所有数据的中央,然而如果咱们要应用数组就得有明确的数据下标对应关系。而那个 nextIndex 原子变量就是保护下标对应关系的要害。

class UnpaddedInternalThreadLocalMap {    static final AtomicInteger nextIndex = new AtomicInteger();    /** Used by {@link FastThreadLocal} */    Object[] indexedVariables;    // ...    public static int nextVariableIndex() {        int index = nextIndex.getAndIncrement();        if (index < 0) {            nextIndex.decrementAndGet();            throw new IllegalStateException("too many thread-local indexed variables");        }        return index;    }

回到 FastThreadLocal 对象中,咱们能够看到这外面有一个动态属性 variablesToRemoveIndex,它的值通过 UnpaddedInternalThreadLocalMap 的动态字段 nextIndex 计算所得。因为 variablesToRemoveIndex 是一个动态属性,而且也是惟一一个动态调用 nextIndex 的中央,所以它的值恒为 0。而每个 FastThreadLocal 对象,都会在构造函数中申请一个新的 index 槽位。

public class FastThreadLocal<V> {    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();    private final int index;    public FastThreadLocal() {        index = InternalThreadLocalMap.nextVariableIndex();    }

当咱们应用 FastThreadLocal 时,比方调用 set 函数,它会先获取 InternalThreadLocalMap 对象,而后依据以后 FastThreadLocal 调配的下标 index,间接设置数组 indexedVariables 中的值。

// FastThreadLocal.java/** * Set the value for the current thread. */public final void set(V value) {    if (value != InternalThreadLocalMap.UNSET) {        // 获取 Map        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();        setKnownNotUnset(threadLocalMap, value);    } else {        remove();    }}/** * @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}. */private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {    if (threadLocalMap.setIndexedVariable(index, value)) {        // 如果设置胜利,则将该 FastThreadLocal 保存起来,不便后续清理        addToVariablesToRemove(threadLocalMap, this);    }}// InternalThreadLocalMap.java/** * @return {@code true} if and only if a new thread-local variable has been created */public boolean setIndexedVariable(int index, Object value) {    Object[] lookup = indexedVariables;    // 如果长度足够,间接通过 index 进行批改    if (index < lookup.length) {        Object oldValue = lookup[index];        lookup[index] = value;        return oldValue == UNSET;    } else {        // 否则进行扩容,扩容后的大小是比 index 大的最小的2的幂        expandIndexedVariableTableAndSet(index, value);        return true;    }}

设置胜利后,会将以后 FastThreadLocal 保留在一个汇合中,之后在进行清理工作时,可能疾速的进行革除。

// FastThreadLocal.java@SuppressWarnings("unchecked")private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {    Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);    Set<FastThreadLocal<?>> variablesToRemove;    // 保留 FastThreadLocal 对象的汇合存在数组的 index 0 地位,因为 variablesToRemoveIndex 恒等于 0    if (v == InternalThreadLocalMap.UNSET || v == null) {        variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());        threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);    } else {        variablesToRemove = (Set<FastThreadLocal<?>>) v;    }    variablesToRemove.add(variable);}/** * Removes all {@link FastThreadLocal} variables bound to the current thread.  This operation is useful when you * are in a container environment, and you don't want to leave the thread local variables in the threads you do not * manage. */public static void removeAll() {    // 在进行革除时,如果InternalThreadLocalMap为空,则阐明没有应用 FastThreadLocal    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();    if (threadLocalMap == null) {        return;    }    try {        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);        // 如果 index0 不存在汇合,阐明没有应用 FastThreadLocal        if (v != null && v != InternalThreadLocalMap.UNSET) {            @SuppressWarnings("unchecked")            Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;            FastThreadLocal<?>[] variablesToRemoveArray =                    variablesToRemove.toArray(new FastThreadLocal[0]);            // 挨个删除汇合中的所有 FastThreadLocal,其中会调用 FastThreadLocal 的 onRemoval 回调函数            for (FastThreadLocal<?> tlv: variablesToRemoveArray) {                tlv.remove(threadLocalMap);            }        }    } finally {        // 将整个 InternalThreadLocalMap 删除        InternalThreadLocalMap.remove();    }}/** * Sets the value to uninitialized for the specified thread local map; * a proceeding call to get() will trigger a call to initialValue(). * The specified thread local map must be for the current thread. */@SuppressWarnings("unchecked")public final void remove(InternalThreadLocalMap threadLocalMap) {    if (threadLocalMap == null) {        return;    }    // 将对应槽位的值设为 null,并返回之前的值    Object v = threadLocalMap.removeIndexedVariable(index);    // 将该 FastThreadLocal 从待删除联合(index0 保留的汇合)中删除    removeFromVariablesToRemove(threadLocalMap, this);    // 如果之前的值不是空,就调用 onRemoval 回调函数    if (v != InternalThreadLocalMap.UNSET) {        try {            onRemoval((V) v);        } catch (Exception e) {            PlatformDependent.throwException(e);        }    }}

至此,FastThreadLocal 的核心内容就介绍完了。这里我有一点费解,为什么 InternalThreadLocalMap 的下标分配器 nextIndex 要申明为动态变量,而不是成员变量呢,如果是成员变量的话,就不会有内存的节约了啊,可能当初这种计划效率会略微好一点吧。然而,当初这种实现很可能每个 InternalThreadLocalMap 中的数组中都会有一些空洞(如果以后线程没有应用所有的 FastThreadLocal)。

最初,还有一点要提一下,就是 InternalThreadLocalMap 为了进一步的提高效率在成员变量中增加了几个填充字段。这是为了避免伪共享。

// InternalThreadLocalMap.java// Cache line padding (must be public)// With CompressedOops enabled, an instance of this class should occupy at least 128 bytes.public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9; 

通常 CPU 的缓存行个别是 64 或 128 字节,为了避免InternalThreadLocalMap的不同实例被加载到同一个缓存行,咱们须要多余填充一些字段,使得每个实例的大小超出缓存行的大小。

下图是计算的根本构造。L1、L2、L3别离示意一级缓存、二级缓存、三级缓存,越凑近CPU的缓存,速度越快,容量也越小。所以L1缓存很小但很快,并且紧靠着在应用它的CPU内核;L2大一些,也慢一些,并且依然只能被一个独自的CPU核应用;L3更大、更慢,并且被单个插槽上的所有CPU核共享;最初是主存,由全副插槽上的所有CPU核共享。

当CPU执行运算的时候,它先去L1查找所需的数据、再去L2、而后是L3,如果最初这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算消耗的工夫就越长。所以如果你在做一些很频繁的事,你要尽量确保数据在L1缓存中。另外,线程之间共享一份数据的时候,须要一个线程把数据写回主存,而另一个线程拜访主存中相应的数据。

Cache是由很多个cache line组成的。每个cache line通常是64字节,对应了主内存中的一块儿地址。CPU每次从主存中拉取数据时,会把相邻的数据也存入同一个cache line。这就有可能有可能Thread1要应用FastThreadLocal1时一次性地将两个内存地址相邻的 FastThreadLocal 对象(FastThreadLocal1,FastThreadLocal2)放入本人的 cache line 中。这时候如果另一个线程Thread2只批改了FastThreadLocal2,之后如果 Thread1 要应用 FastThreadLocal1 也须要从主存中从新拉取(因为 FastThreadLocal1 和 FastThreadLocal2 在同一个缓存行中,只有缓存行内的任意地位的数据被批改,那么其余线程就须要从主存中拉取最新的缓存行数据之后能力应用)。这种无奈充沛应用缓存行个性的景象,称为伪共享。