介绍现代计算机系统中,可以有多个CPU,每个CPU可以有多个核心。为了充分利用现代CPU的功能,在JAVA中引入了多线程,不同的线程可以同时运行在不同的CPU或者不同的CPU核上。但是对于JAVA程序员来说,创建多少个线程是可以自己控制的,但是线程运行在哪个CPU上是一个黑盒子,一般很难知道。但是,如果不同的CPU核心调度同一个线程,可能会出现CPU切换带来的性能损失。一般情况下,这个损失比较小,但是如果你的程序特别在意这种CPU切换带来的损失,可以试试今天要讲的JavaThreadAffinity。Java线程亲和性简介JAVA代码中的线程与CPU特定的核心绑定,以提高程序性能。显然,java线程Affinity为了与底层CPU进行交互,必须使用JAVA与native方法进行交互的方式。JNI虽然是JAVA和JAVA原生方法交互的官方方法,但是JNI使用起来比较麻烦。.所以javathreadAffinity其实使用的是JNA,它是在JNI基础上改进的一个库,用来和native方法进行交互。首先介绍一下CPU中的几个概念,即CPU、CPUsocket和CPUcore。首先是中央处理器。CPU的全称是CentralProcessingUnit,又称中央处理器,是任务处理的关键核心。那么什么是CPU插槽呢?所谓插座就是插CPU的插座。如果你组装过台式电脑,你应该知道CPU是安装在插座上的。CPUCore是指CPU中的核心数。很久以前,CPU是单核的,但是随着多核技术的发展,一个CPU可以包含多个核,CPU中的核才是真正的业务处理。单元。如果是linux机器,可以使用lscpu命令查看系统的CPU状态,如下:Architecture:x86_64CPUop-mode(s):32-bit,64-bitByteOrder:LittleEndianCPU(s):1在线CPU列表:0每个内核线程:每个插槽1个内核:1个插槽:1个NUMA节点:1个供应商ID:GenuineIntelCPU系列:6型号:94型号名称:Intel(R)Xeon(R)Gold6148CPU@2.40GHzStepping:3CPUMHz:2400.000BogoMIPS:4800.00Hypervisorvendor:KVMVirtualizationtype:fullL1dcache:32KL1icache:32KL2cache:4096KL3cache:28160KNUMAnode0CPU(s):0Flags:fpuvmecdepsemsrpaemcecx8apicsepmtrrpgemcacmovpatpse36clflushmmxfxsrssesse2ss系统调用nxpdpe1gbrdtscplmconstant_tscrep_goodnopleagerfpupnibepclmulqdqssse3fmacx16pcidsse4_1sse4_2movmovpopcnttsc_deadline_timeraesxsaveavxf16crdrandhypervisorlahf_lmabm3dnowprefetchinvpcid_singlefsgsbasebmi1hleavx2smepbmi2ermsinvpcidrtmmpxavx512favx512dqrdseedadxsmapavx512cdavx512bwavx512vlxsaveoptxsavecxgetbv1arat从上面的输出我们可以看到,这个服务器有一个socket,每每个socket都有一个核心,每个核心可以同时处理1个线程。这些CPU的信息可以称为CPU布局。在Linux中,CPU的布局信息存放在/proc/cpuinfo中。JavaThreadAffinity中有一个CpuLayout接口对应这个信息:publicinterfaceCpuLayout{intcpus();国际套接字();intcoresPerSocket();intthreadsPerCore();intsocketId(intcpuId);intcoreId(intcpuId);intthreadId(intcpuId);}AffinityStrategies根据CPU布局的信息,提供了一些基本的Affinity策略来安排不同线程之间的分布关系,主要如下:SAME_CORE——运行在同一个核心上。SAME_SOCKET-在同一个套接字上运行,但不在同一个内核上。DIFFERENT_SOCKET-在不同的套接字上运行DIFFERENT_CORE-在不同的内核上运行ANY-任何情况都可以。这些策略也是根据CpuLayout的socketId和coreId来区分的。下面以SAME_CORE为例,按其具体实现:SAME_CORE{@Overridepublicbooleanmatches(intcpuId,intcpuId2){返回cpuLayout.socketId(cpuId)==cpuLayout.socketId(cpuId2)&&cpuLayout.coreId(cpuId)==cpuLayout.coreId(cpuId2);}}亲和策略是可以排序的,先匹配上一个策略,不匹配则选择第二个策略,以此类推。AffinityLock的使用接下来我们看一下Affinity的具体使用。首先是获得CPU锁。在JAVA7之前,我们可以这样写:AffinityLockal=AffinityLock.acquireLock();try{//dosomeworklockedtoaCPU.}finally{al.release();}在JAVA7之后,你可以这样写:try(AffinityLockal=AffinityLock.acquireLock()){//在锁定时做一些工作到CPU。}acquireLock方法可以获得任何可用的cpu。这是一个粗粒度的锁。如果你想获得一个细粒度的核心,你可以使用acquireCore:try(AffinityLockal=AffinityLock.acquireCore()){//dosomeworkwhilelockedtoaCPU.}acquireLock还有一个bind参数,表示是否当前线程绑定到获得的cpu锁上,如果bind参数=true,那么当前线程将运行在acquireLock中获得的CPU上。如果bind参数=false,表示acquireLock会在以后的某个时间点绑定。上面我们提到了AffinityStrategy,这个AffinityStrategy可以作为acquireLock的参数:通过调用当前AffinityLock的acquireLock方法,当前线程可以Allocate一个与之前的锁策略相关的AffinityLock。AffinityLock还提供了一个dumpLocks方法来查看当前CPU和线程的绑定状态。让我们举个例子:privatestaticfinalExecutorServiceES=Executors.newFixedThreadPool(4,newAffinityThreadFactory("bg",SAME_CORE,DIFFERENT_SOCKET,ANY));对于(inti=0;i<12;i++)ES.submit(newCallable
