在计算机系统中,内存是以缓存行为单位存储的,一个缓存行存储的字节数是2的倍数。在不同的机器上,缓存行大小从32字节到256字节不等,一般为64字节。伪共享是指当多个线程同时读写同一缓存行的不同变量时,虽然这些变量之间没有任何关系,但仍然需要多个线程之间进行同步,从而导致性能下降。在具有对称多处理器结构的系统中,伪共享是影响性能的主要因素之一。由于通过codewalkthrough很难定位falsesharing的问题,所以大家把falsesharing称为“性能杀手”。为了通过增加线程数来实现计算能力的水平扩展,我们必须保证多个线程不能同时读写一个变量或缓存行。我们可以通过代码走查定位到多线程读写一个变量的情况。但是,要想知道多线程读写同一个缓存行的情况,首先要了解系统内存的组织结构,如下图所示。从上图可以看出系统的缓存结构。线程1在CPUcore1上读写变量X,线程2在CPUcore2上读写变量Y。不幸的是,变量X和变量Y在同一个cacheline上。为了对缓存行进行读写,每个线程都必须竞争并获得缓存行的读写权限。如果线程2在cpucore2上获得了cacheline的读写权限,那么thread1必须flushitscache只有在core1上获得读写权限后,这才导致cacheline通过交换最新的副本数据不同线程之间L3缓存多次,这极大地影响了多核CPU的性能。如果这些CPU内核位于不同的插槽上,性能会变得更差。现在,我们了解JVM对象的内存模型。所有的Java对象都有一个8字节的对象头,前四个字节用来存放对象的哈希码和锁定状态,前三个字节用来存放哈希码,最后一个字节用来存放锁定状态,一旦对象被锁定,这4个字节将从对象中取出并与指针链接。剩下的4个字节用于存储对对象所属类的引用。对于数组,还有一个变量保存数组的大小,即4个字节。每个对象的大小会对齐到8字节的倍数,不足8字节的部分需要补齐。为了保证效率,Java编译器在编译Java对象时,会按照字段类型对Java对象的字段进行排序,如下表所示。因此,我们可以通过将长整型变量填充在不同的缓存行中来隔离任意字段之间的热变量,通过减少误同步,在多核CPU中可以大大提高效率。下面,我们用一个测试用例来证明我们理论分析的正确性,参考下面的代码段。packagecom.robert.concurrency.cacheline;/****@author:李彦鹏*@since:Jun11,20171:01:29AM*@version:1.0*/publicfinalclassFalseSharingDemo{//测试线程数privatefinalstaticintNUM_THREADS=4;//测试次数privatefinalstaticintNUM_TEST_TIMES=10;//对象类无填充且无缓存行对齐staticclassPlainHotVariable{publicvolatilelongvalue=0L;}//对象类有填充且缓存行对齐staticfinalclassAlignHotVariableextendsPlainHotVariable{publiclongp1,p2,p3,p4,p5,p6;}staticfinalclassCompetitorThreadextendsThread{privatefinalstaticlongITERATIONS=500L*1000L*1000L;privatePlainHotVariableplainHotVariable;publicCompetitorThread(finalPlainHotVariableplainHotVariable){this.plainHotVariable=plainHotVariable;}@Overridepublicvoidrun(){//一个线程对一个变量进行大量的存取操作for(inti=0;it2;}publicstaticvoidrunOneSuit(intthreadsNum,inttestNum)throwsException{intexpectedCount=0;for(inti=0;iAlign):60.0%2线程Plain:17403262079Align:3946343388Plain:3868304700Align:3650775431Plain:12111598939Align:4224862180Plain:4805070043Align:4130750299Plain:15889926613Align:3901238050Plain:12059354004Align:3771834390Plain:16744207113Align:4433367085Plain:4090413088Align:3834753740Plain:11791092554Align:3952127226Plain:12125857773Align:4140062817Radio(Plain>Align):100.0%4线程:Plain:12714212746Align:7462938088Plain:12865714317Align:6962498317Plain:18767257391Align:7632201194Plain:12730329600Align:6955849781Plain:12246997913Align:7457147789Plain:17341965313Align:7333927073Plain:19363865296Align:7323193058Plain:12201435415Align:7279922233Plain:12810166367Align:7613635297Plain:19235104612Align:7398148996Radio(Plain>Align):100.0%从上面的测试结果可以看出,用filledarrays测试的时间普遍少于用unfilledarrays测试的时间,并且as线程数增加,使用未填充数组的场景性能下降,扩展性越来越弱,如下图。虽然我们并不确切知道系统是如何分配我们的对象的,但是,我们的测试结果验证了我们理论分析的正确性。其实大名鼎鼎的无锁队列Disruptor就是通过解决伪竞争的问题来提高效率的。它通过在RingBuffer游标和BatchEventProcessor序列变量后面填充变量使其与64字节缓存行对齐来解决伪竞争问题。问题。上面我们看到cacheline机制在多线程环境下会产生伪同步。现在,让我们学习另一个缓存行影响性能的例子。代码如下。packagecom.robert.concurrency.cacheline;/****@author:李彦鹏*@since:Jun11,20171:01:29AM*@version:1.0*/publicfinalclassCacheLineDemo{//缓存行大小为64字节,ieFor8longintegralprivatefinalstaticintCACHE_LINE_LONG_NUM=8;//测试缓存行数privatefinalstaticintLINE_NUM=1024*1024;//一次测试次数privatefinalstaticintNUM_TEST_TIMES=10;//构造一个可以填充LINE_NUM缓存行的数组privatestaticfinallong[]values=newlong[CACHE_LINE_LONG_NUM*LINE_NUM];publicstaticlongrunOneTestWithAlign(){finallongstart=System.nanoTime();//顺序读测试,期望系统在访问每个缓存行的第一个长整型变量时自动缓存整个缓存行,后续对该行的访问会命中cachefor(inti=0;i