小猫他爹 發表於 2025-10-30 15:42:00

java并发性能陷阱--伪共享

<p><span style="font-family: &quot;Microsoft YaHei&quot;">缓存可以说是计算机领域最伟大的发明之一,</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">经常会有人问,缓存是越多越好么?</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">一般人们都会斩钉截铁的回答不是。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">至于为什么?</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">往往无法直觉回答了,可能会从缓存一致性,空间占用等几个角度逐一分析。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">今天就来看看由于一致性导致的缓存问题。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">在之前的文章中,我们聊过JMM java的内存模型(一定要有所了解,不太清楚的同学可以看下前文链接https://www.cnblogs.com/jilodream/p/9452391.html),可以知道线程并不是直接读写内存,而是调用线程自己的工作空间。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">但这只是一个逻辑模型,线程我们可以理解为cpu的核心,工作空间所对应的位置一般是指cpu的缓存。就像下图这样:</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202510/704073-20251030151821958-2105278573.png" alt="jmmcpu" loading="lazy"></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">目前主流的cpu就是每个核心有自己的多级缓存,一般还会加一个共享缓存,</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">越靠近核,缓存越小,但越快,成本也越大。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">java线程实际对应的就是这个核,工作空间对应的就是这个缓存。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">如果你详细思考,就会考虑到缓存中的数据时如何加载变量的。毕竟变量又长有短,如何加载定位的?</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">一般来说,我们将内存划分成若干的块,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )每一块是64个字节(主流是这个大小)</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">同时我们将缓存划分成若干的缓存行,也是64个字节。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">cpu每次加载时,不是按照某个变量加载,而是将已经划分好的整块内容直接加载到缓存行中。因为从数据的使用经验来看,一般我们在使用某个变量时,很大可能会使用邻近变量,这种缓存的预判加载,提高了缓存的命中率。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">有小伙伴会有疑问,会不会不同核的缓存行加载的数据跨了内存块了,也就是A核的缓存行是 xyz变量,B核的缓存行是yza变量。这是不会的,缓存块是根据内存的地址和偏移量划分好的,不会根据不同核来划分不同的边界的。</span></p>
<p><img src="https://img2024.cnblogs.com/blog/704073/202510/704073-20251030152005462-845435542.png" alt="cacheline1" loading="lazy"></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">做过缓存设计的同学肯定知道,在设计时一定要考虑数据一致性的问题。如果多份缓存以及主存之间的数据不一致,就无法并发处理,无法得到准确的结果。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">(ps,cpu一般是通过MESI缓存一致性协议并且配合失效缓存队列等等来实现的,感兴趣的读者可以查下相关内容)</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">从前文中的java内存模型中可以知道,当volatile变量发生变化时,java通过内存屏障,来强制失效其它cpu核心中缓存。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">但是在真实情况下,cpu是按照行来缓存变量的,而不是单个变量,此时标记失效的就是整个缓存行。那么就会出现类似一个情况:</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">线程1操作变量a,线程2操作变量b,根据缓存的加载机制</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(1)两者的均加载同一段缓存行。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(2)当线程1 修改完变量a时,通知其它线程失效该缓存行</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">(3)线程2修改变量b,发现缓存行失效,重新加载缓存行,修改完变量b后,重新知会其它线程该行已经失效</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">这样当线程1每次修改变量a,线程2每次修改变量b时,当前缓存都不断的需要重新加载,本质上已经失去了缓存的意义,还增加了缓存状态控制,缓存重新加载的开销。</span><br><strong><span style="font-family: &quot;Microsoft YaHei&quot;">这种在相同的缓存行的多个变量,但是由于并发原因,导致缓存不断失效,无法利用缓存读取变量的场景,我们就称之为伪共享。(False Sharing)</span></strong></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;"><img src="https://img2024.cnblogs.com/blog/704073/202510/704073-20251030152058643-2028293432.png" alt="cacheline2" loading="lazy"></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">这种情况其实不仅仅是java,其它语言,甚至是多缓存的业务,都会有类似的问题。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">即由于并发引起的缓存联动失效,即使对我当前业务没有实际影响,但是由于缓存一致性的协议设计,我们判断当前缓存已经脏了。我们就需要重新加载。缓存的优势丧失,成本却被无限放大。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">就像下边这个例子:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">缓存类:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CacheA {
</span><span style="color: rgba(0, 128, 128, 1)">2</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">a;
</span><span style="color: rgba(0, 128, 128, 1)">3</span>
<span style="color: rgba(0, 128, 128, 1)">4</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">b;
</span><span style="color: rgba(0, 128, 128, 1)">5</span> }</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">线程类:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Main {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> CacheA <strong><span style="color: rgba(255, 0, 0, 1)">cache = new CacheA();
</span></strong><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TOTAL = 1000000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>         Runnable r1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">10</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>                     cache.a = (i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">13</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>               System.out.println("方法耗时1: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>         Runnable r2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)">22</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)">23</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">24</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">25</span>                     cache.b =(i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">27</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">28</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>               System.out.println("方法耗时2: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">33</span>
<span style="color: rgba(0, 128, 128, 1)">34</span>         Thread t1=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r1);
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)">      t1.start();
</span><span style="color: rgba(0, 128, 128, 1)">36</span>
<span style="color: rgba(0, 128, 128, 1)">37</span>         Thread t2=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r2);
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)">      t2.start();
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">40</span> }</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">代码逻辑是两个线程并发修改两个变量,这两个变量在同一个实例里边。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">输出结果是这样的:</span></p>
<div class="cnblogs_code">
<pre>Connected to the target VM, address: '127.0.0.1:58237', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">
方法耗时2: </span>19<span style="color: rgba(0, 0, 0, 1)"> 毫秒
方法耗时1: </span>22<span style="color: rgba(0, 0, 0, 1)"> 毫秒
Disconnected from the target VM, address: </span>'127.0.0.1:58237', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">

Process finished with exit code </span>0</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">我们来修改代码,加上很多无效的变量,重新执行,</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">缓存类:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CacheB {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">a;
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp1=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp2=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp3=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp4=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp5=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp6=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   <span style="color: rgba(0, 0, 255, 1)">long</span> temp7=0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">10</span>
<span style="color: rgba(0, 128, 128, 1)">11</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">b;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> }</pre>
</div>
<p>线程类:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Main {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <strong><span style="color: rgba(255, 0, 0, 1)">CacheB cache = new CacheB();
</span></strong><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TOTAL = 1000000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>         Runnable r1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">10</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>                     cache.a = (i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">13</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>               System.out.println("方法耗时1: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>         Runnable r2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)">22</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)">23</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">24</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">25</span>                     cache.b =(i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">27</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">28</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>               System.out.println("方法耗时2: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">33</span>
<span style="color: rgba(0, 128, 128, 1)">34</span>         Thread t1=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r1);
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)">      t1.start();
</span><span style="color: rgba(0, 128, 128, 1)">36</span>
<span style="color: rgba(0, 128, 128, 1)">37</span>         Thread t2=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r2);
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)">      t2.start();
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">40</span> }</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">执行结果如下:</span></p>
<div class="cnblogs_code">
<pre>Connected to the target VM, address: '127.0.0.1:58389', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">
方法耗时1: </span>10<span style="color: rgba(0, 0, 0, 1)"> 毫秒
方法耗时2: </span>10<span style="color: rgba(0, 0, 0, 1)"> 毫秒
Disconnected from the target VM, address: </span>'127.0.0.1:58389', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">

Process finished with exit code </span>0</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">是不是很神奇,我们给一个对象加了很多无用的变量,它居然变快了。而且性能还提升了不少。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">这个优化的核心思路就是通过强制指定内存相对位置,将不相关的变量强制分配到不同的缓存行上,让缓存行不会因为当前不使用的缓存而被强制失效。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">很多人也喜欢这样子写:</span></p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> value;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> p1, p2, p3, p4, p5, p6, p7;</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">通过手动补齐剩余字节,确保当前变量尽可能在一个缓存行上。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">但是这样子写代码就很不方便了,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )我们要增加很多无意义的字段,或者通过其它变量穿插起来。很容易被别人误改,误删,也影响代码最重要的阅读性。</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">因此java在8及以上的版本,增加了一个注解@Contended</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">Contended</span><br><span style="font-family: &quot;Microsoft YaHei&quot;">美 英 </span><br><span style="font-family: &quot;Microsoft YaHei&quot;">v.竞争;认为;争夺</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">这个注解既可以用在类上,也可以用在变量上代码如下:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">缓存类:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> jdk.internal.vm.annotation.Contended;
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 128, 0, 1)">/**</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 128, 0, 1)"> * @discription
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CacheC {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">    @Contended
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">a;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">    @Contended
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">b;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> }</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">线程执行类:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Main {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> CacheC cache = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CacheC();
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> TOTAL = 1000000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>         Runnable r1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">10</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>                     cache.a = (i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">13</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>               System.out.println("方法耗时1: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>         Runnable r2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Runnable() {
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">            @Override
</span><span style="color: rgba(0, 128, 128, 1)">22</span>             <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
</span><span style="color: rgba(0, 128, 128, 1)">23</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> System.currentTimeMillis();
</span><span style="color: rgba(0, 128, 128, 1)">24</span>               <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; TOTAL; i++<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">25</span>                     cache.b =(i-99999)*(i+99999<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">27</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> endTime = System.currentTimeMillis();   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 结束时间(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">28</span>               <span style="color: rgba(0, 0, 255, 1)">long</span> cost = endTime - startTime;         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 耗时(毫秒)</span>
<span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>               System.out.println("方法耗时2: " + cost + " 毫秒"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">      };
</span><span style="color: rgba(0, 128, 128, 1)">33</span>
<span style="color: rgba(0, 128, 128, 1)">34</span>         Thread t1=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r1);
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)">      t1.start();
</span><span style="color: rgba(0, 128, 128, 1)">36</span>
<span style="color: rgba(0, 128, 128, 1)">37</span>         Thread t2=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Thread(r2);
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)">      t2.start();
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">40</span> }</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">同时我们要在jdk 启动时配上虚拟机参数:</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">-XX:-RestrictContended </span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">这个配置参数表示启用Contended注解</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">同时IDEA等(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )工具还会提示我们在配置中开启编译选项开关,允许代码访问jdk内部/隐藏的api</span></p>
<div class="cnblogs_code">
<pre>--add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED</pre>
</div>
<p>使用注解后,执行结果如下</p>
<div class="cnblogs_code">
<pre>Connected to the target VM, address: '127.0.0.1:56688', transport: 'socket'<span style="color: rgba(0, 0, 0, 1)">
方法耗时2: </span>11<span style="color: rgba(0, 0, 0, 1)"> 毫秒
方法耗时1: </span>12<span style="color: rgba(0, 0, 0, 1)"> 毫秒
Disconnected from the target VM, address: </span>'127.0.0.1:56688', transport: 'socket'</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">和手动补齐的速度差不多。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">手动补齐易于控制,但是影响代码阅读,交给虚拟机自动补齐。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">通过注解交给jvm来强制填充隔离,又因为是内部API,不保证稳定性,因此大家根据自己情况来选用。</span></p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <p style="background: #C0FFFF; padding: 10px; border: 1px dashed #E0E0E0; font: 100% 微软雅黑; color: #F00; text-align: center">
如果你觉得写的不错,欢迎转载和点赞。
转载时请保留作者署名jilodream/王若伊_恩赐解脱(博客链接:http://www.cnblogs.com/jilodream/</p><br><br>
来源:https://www.cnblogs.com/jilodream/p/19177246
頁: [1]
查看完整版本: java并发性能陷阱--伪共享