DavidJohnWheeler有一句名言“计算机科学中的任何问题都可以通过添加一层间接来解决”,如果一层还不够的话,再添加一层。后半句是我加的(* ̄︶ ̄)。虽然有点玩笑,但确实能说明一些问题。计算机科学确实通过层层抽象和封装来解决大量问题。简单回顾一下:一开始,程序员直接输入二进制指令来操作硬件,不仅性能低下,而且占用用户大量时间;后来出现了操作系统,用文件、进程和线程、地址空间来抽象磁盘、CPU和内存,统一和简化硬件访问方式;机器语言对用户不友好,因此出现了汇编语言、中间语言(如C)、高级语言(如Java)的封装,其最终执行仍需转换为机器语言;赤裸裸的高级语言大家还在用,觉得开发效率低下,于是各种框架(如Spring,Hibernate)应运而生……经过这么一层抽象包装,我们想实现一个function比如,定时写文件已经成为一件很简单的事情,只需要几行代码就可以搞定。但是过多的抽象层有时候会给我们的顶层用户带来一些莫名其妙的问题。下面用一个真实的虚假共享案例来说明publicclassFalseSharing{privatestaticAtomicLongtime=newAtomicLong(0);publicstaticvoidmain(String...args)throwsInterruptedException{inttestNum=50;for(inti=0;i0){objArray[ii].aLong+=1L;}countDownLatch.countDown();}});thread.start();}尝试{countDownLatch.await();}catch(InterruptedExceptione){e.printStackTrace();}longend=System.nanoTime();time.getAndAdd(结束-开始);}}@ContendedprivatestaticfinalclassObj{privatevolatilelongaLong=8L;//8Bytes//privatevolatilelonga=2L,b=2L,c=2L,d=2L,e=2L,f=2L,g=2L;//*****}}所有的代码都在这里,为了避免JavaJIT(也是一层抽象)的影响,我们每次执行都要加上参数-Xint到强制使用解释模式在我的机器上(4core,8processor,Core-i7),直接运行这段代码得到result1是4594us,avg的水平,在result1的基础上取消注释//*****行得到结果2是3916us的电平,avg.在结果1的基础上,在运行参数中加入-XX:-RestrictContended,使@Contended生效,结果3为3466us,avg。这时候顶级用户会莫名其妙。为什么通过添加更多字段来减少运行时间?添加@Contended后时间怎么能更短呢?从Java代码层面的抽象来看,完全没有问题,那么问题出在哪里呢?我们知道,CPU中的每个核都有自己的Cache,高层的L1是自己私有的,低层的L2、L3等可能是私有的,也可能是不同核共享的。这些不同级别的缓存(一次访问时间大约是几ns)用来弥补CPU快(一次访问时间通常是零点几ns)和内存访问速度慢(一次访问时间几十ns))Gap,并以CacheLineSize:NBytes(Core-i7为64)为基本单位,根据局部性原则,一次性将内存中访问变量周围NBytes的内容复制到Cache中,如果一个对象不够NBytes,有可能多个对象共享一个CacheLine,这样一个线程刷新Cacheline,就会使其他线程的缓存失效。到更下层的Cache甚至内存访问都会大大降低访问速度。那么回到刚才的问题,多加几个字段可以在一定程度上增加对象占用的空间,减少共享CacheLine的机会,所以访问时间减少,@Contended使一个对象成为CacheLine,直接帮助我们避免虚假共享,因此访问时间更少。要解决这个问题,仅仅知道Java的抽象层次(语法、JDKAPI等)是不可能的。还要了解操作系统中的抽象层次,甚至是CPU芯片的原理。再比如,JVM帮我们把C/C++的手动内存管理封装了一层抽象,实现了内存的自动管理,从而解放了我们。当然,我们用的很好,但是如果不了解这层抽象和封装,那程序OOM的时候你就只能傻眼了。最后总结一下,计算机科学中的任何问题都可以通过加一层间接来解决,这是非常正确的,但也正是因为层层抽象和封装,所以很难定位到问题所在,你甚至不知道问题出在哪一层。所以,要想提高自己的技术水平,不仅要知道为什么(看顶层包装),还要知道为什么(看底层包装)。很大程度上可以凭直觉定位。即使不能使用直觉,也可以通过各种手段进行调试。只有最顶层的抽象,很多时候只能被bug看。查阅原文,来自MageekChiu。