简介

在古代计算机系统中,能够有多个CPU,每个CPU又能够有多核。为了充分利用古代CPU的性能,JAVA中引入了多线程,不同的线程能够同时在不同CPU或者不同CPU核中运行。然而对于JAVA程序猿来说创立多少线程是能够本人管制的,然而线程到底运行在哪个CPU上,则是一个黑盒子,一般来说很难得悉。

然而如果是不同CPU核查同一线程进行调度,则可能会呈现CPU切换造成的性能损失。个别状况下这种损失是比拟小的,然而如果你的程序特地在意这种CPU切换带来的损耗,那么能够试试明天要讲的Java Thread Affinity.

Java Thread Affinity简介

java thread Affinity是用来将JAVA代码中的线程绑定到CPU特定的核上,用来晋升程序运行的性能。

很显然,要想和底层的CPU进行交互,java thread Affinity肯定会用到JAVA和native办法进行交互的办法,JNI尽管是JAVA官网的JAVA和native办法进行交互的办法,然而JNI在应用起来比拟繁琐。所以java thread Affinity理论应用的是JNA,JNA是在JNI的根底上进行改进的一种和native办法进行交互的库。

先来介绍CPU中几个概念,别离是CPU,CPU socket和CPU core。

首先是CPU,CPU的全称就是central processing unit,又叫做中央处理器,就是用来进行工作解决的要害外围。

那么什么是CPU socket呢?所谓socket就是插CPU的插槽,如果组装过台式机的同学应该都晓得,CPU就是装置在Socket上的。

CPU Core指的是CPU中的核数,在很久之前CPU都是单核的,然而随着多核技术的倒退,一个CPU中能够蕴含多个核,而CPU中的核就是真正的进行业务解决的单元。

如果你是在linux机子上,那么能够通过应用lscpu命令来查看零碎的CPU状况,如下所示:

Architecture:          x86_64CPU op-mode(s):        32-bit, 64-bitByte Order:            Little EndianCPU(s):                1On-line CPU(s) list:   0Thread(s) per core:    1Core(s) per socket:    1Socket(s):             1NUMA node(s):          1Vendor ID:             GenuineIntelCPU family:            6Model:                 94Model name:            Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHzStepping:              3CPU MHz:               2400.000BogoMIPS:              4800.00Hypervisor vendor:     KVMVirtualization type:   fullL1d cache:             32KL1i cache:             32KL2 cache:              4096KL3 cache:              28160KNUMA node0 CPU(s):     0Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat

从下面的输入咱们能够看到,这个服务器有一个socket,每个socket有一个core,每个core能够同时解决1个线程。

这些CPU的信息能够称为CPU layout。在linux中CPU的layout信息是寄存在/proc/cpuinfo中的。

在Java Thread Affinity中有一个CpuLayout接口用来和这些信息进行对应:

public interface CpuLayout {        int cpus();    int sockets();    int coresPerSocket();    int threadsPerCore();    int socketId(int cpuId);    int coreId(int cpuId);    int threadId(int cpuId);}

依据CPU layout的信息, AffinityStrategies提供了一些根本的Affinity策略,用来安顿不同的thread之间的散布关系,次要有上面几种:

    SAME_CORE - 运行在同一个core中。    SAME_SOCKET - 运行在同一个socket中,然而不在同一个core上。    DIFFERENT_SOCKET - 运行在不同的socket中    DIFFERENT_CORE - 运行在不同的core上    ANY - 任何状况都能够

这些策略也都是依据CpuLayout的socketId和coreId来进行辨别的,咱们以SAME_CORE为例,按下它的具体实现:

SAME_CORE {        @Override        public boolean matches(int cpuId, int cpuId2) {            CpuLayout cpuLayout = AffinityLock.cpuLayout();            return cpuLayout.socketId(cpuId) == cpuLayout.socketId(cpuId2) &&                    cpuLayout.coreId(cpuId) == cpuLayout.coreId(cpuId2);        }    }

Affinity策略能够有程序,在后面的策略会首先匹配,如果匹配不上则会抉择第二策略,依此类推。

AffinityLock的应用

接下来咱们看下Affinity的具体应用,首先是取得一个CPU的lock,在JAVA7之前,咱们能够这样写:

AffinityLock al = AffinityLock.acquireLock();try {     // do some work locked to a CPU.} finally {     al.release();}

在JAVA7之后,能够这样写:

try (AffinityLock al = AffinityLock.acquireLock()) {    // do some work while locked to a CPU.}

acquireLock办法能够为线程取得任何可用的cpu。这个是一个粗粒度的lock。如果想要取得细粒度的core,能够用acquireCore:

try (AffinityLock al = AffinityLock.acquireCore()) {    // do some work while locked to a CPU.}

acquireLock还有一个bind参数,示意是否将以后的线程绑定到取得的cpu lock上,如果bind参数=true,那么以后的thread会在acquireLock中取得的CPU上运行。如果bind参数=false,示意acquireLock会在将来的某个时候进行bind。

下面咱们提到了AffinityStrategy,这个AffinityStrategy能够作为acquireLock的参数应用:

    public AffinityLock acquireLock(AffinityStrategy... strategies) {        return acquireLock(false, cpuId, strategies);    }

通过调用以后AffinityLock的acquireLock办法,能够为以后的线程调配和之前的lock策略相干的AffinityLock。

AffinityLock还提供了一个dumpLocks办法,用来查看以后CPU和thread的绑定状态。咱们举个例子:

private static final ExecutorService ES = Executors.newFixedThreadPool(4,           new AffinityThreadFactory("bg", SAME_CORE, DIFFERENT_SOCKET, ANY));for (int i = 0; i < 12; i++)            ES.submit(new Callable<Void>() {                @Override                public Void call() throws InterruptedException {                    Thread.sleep(100);                    return null;                }            });        Thread.sleep(200);        System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks());        ES.shutdown();        ES.awaitTermination(1, TimeUnit.SECONDS);

下面的代码中,咱们创立了一个4个线程的线程池,对应的ThreadFactory是AffinityThreadFactory,给线程池起名bg,并且调配了3个AffinityStrategy。 意思是首先调配到同一个core上,而后到不同的socket上,最初是任何可用的CPU。

而后具体执行的过程中,咱们提交了12个线程,然而咱们的Thread pool最多只有4个线程,能够预感, AffinityLock.dumpLocks办法返回的后果中只有4个线程会绑定CPU,一起来看看:

The assignment of CPUs is0: CPU not available1: Reserved for this application2: Reserved for this application3: Reserved for this application4: Thread[bg-4,5,main] alive=true5: Thread[bg-3,5,main] alive=true6: Thread[bg-2,5,main] alive=true7: Thread[bg,5,main] alive=true

从输入后果能够看到,CPU0是不可用的。其余7个CPU是可用的,然而只绑定了4个线程,这和咱们之前的剖析是匹配的。

接下来,咱们把AffinityThreadFactory的AffinityStrategy批改一下,如下所示:

new AffinityThreadFactory("bg", SAME_CORE)

示意线程只会绑定到同一个core中,因为在以后的硬件中,一个core同时只能反对一个线程的绑定,所以能够预感最初的后果只会绑定一个线程,运行后果如下:

The assignment of CPUs is0: CPU not available1: Reserved for this application2: Reserved for this application3: Reserved for this application4: Reserved for this application5: Reserved for this application6: Reserved for this application7: Thread[bg,5,main] alive=true

能够看到只有第一个线程绑定了CPU,和之前的剖析相匹配。

应用API间接调配CPU

下面咱们提到的AffinityLock的acquireLock办法其实还能够承受一个CPU id参数,间接用来取得传入CPU id的lock。这样后续线程就能够在指定的CPU上运行。

    public static AffinityLock acquireLock(int cpuId) {        return acquireLock(true, cpuId, AffinityStrategies.ANY);    }

实时上这种Affinity是寄存在BitSet中的,BitSet的index就是cpu的id,对应的value就是是否取得锁。

先看下setAffinity办法的定义:

    public static void setAffinity(int cpu) {        BitSet affinity = new BitSet(Runtime.getRuntime().availableProcessors());        affinity.set(cpu);        setAffinity(affinity);    }

再看下setAffinity的应用:

long currentAffinity = AffinitySupport.getAffinity();Affinity.setAffinity(1L << 5); // lock to CPU 5.
留神,因为BitSet底层是用Long来进行数据存储的,所以这里的index是bit index,所以咱们须要对十进制的CPU index进行转换。

总结

Java Thread Affinity能够从JAVA代码中对程序中Thread应用的CPU进行管制,十分弱小,大家能够使用起来。

本文已收录于 http://www.flydean.com/01-java-thread-affinity/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!