当前位置: 首页 > 后端技术 > Java

多处理器编程艺术阅读笔记-硬件基础1

时间:2023-04-01 14:15:39 Java

本系列是多处理器编程艺术的阅读笔记。以原书为基础,结合OpenJDK11及以上版本的代码进行理解和实现。并且根据个人信息搜索和了解的经验,我会分享一些个人信息给想了解的朋友。有兴趣,那强烈推荐大家搜索一下CSE502ComputerArchitecture的课件。看了这门课的CPU和缓存章节,受益匪浅。这里列出的硬件基础知识主要是为了让我们了解编程语言的设计以提高性能。一般我们可以很容易的写出适合单处理器运行的代码,但是在多处理器的情况下,同样的代码可能效率会低很多。请看下面的例子:假设两个线程需要访问一个资源,而这个资源不能同时被多个线程访问,访问前必须先加锁,获得锁后才能访问该资源,而访问资源后需要释放锁。这里我们使用一个布尔字段来实现锁。如果布尔值是假的,锁是空闲的,否则它正在被使用。使用compareAndSet获取锁。如果此调用返回true,则修改成功,即成功获取锁,如果返回false,则修改失败,即获取锁失败。假设我们有这个锁的以下接口:publicinterfaceLock{voidlock();voidunlock();}对于这个锁,我们有以下两种实现方式,第一种是:importjava.lang.invoke.MethodHandles;importjava.lang.invoke.VarHandle;公共类TASLock实现Lock{privatebooleanlocked=false;//操作锁的句柄privatestaticfinalVarHandleLOCKED;static{try{//初始化句柄LOCKED=MethodHandles.lookup().findVarHandle(TASLock.class,"locked",boolean.class);}catch(Exceptione){thrownewError(e);}}@Overridepublicvoidlock(){//compareAndSet成功表示已经获取到锁while(!LOCKED.compareAndSet(this,false,true)){//放弃CPU资源,这是目前最好的方式实现SPIN放弃CPU。当线程数远大于CPU数时,效果优于Thread.yield,从及时性角度效果远优于Thread.sleepThread.onSpinWait();}}@Overridepublicvoidunlock(){//需要volatile更新,让其他线程感知LOCKED.setVolatile(this,false);}}另一个锁实现是:publicclassTTASLockimplements锁定{privatebooleanlocked=false;//操作锁定句柄privatestaticfinalVarHandleLOCKED;static{try{//初始化句柄LOCKED=MethodHandles.lookup().findVarHandle(TTASLock.class,"locked",boolean.class);}catch(Exceptione){thrownewError(e);}}@Overridepublicvoidlock(){while(true){//普通读锁,如果被占用,则一直为SPINwhile((boolean)LOCKED.get(this)){//放弃CPU资源,这是目前实现SPIN放弃CPU的最好方式。当线程数远大于CPU数时,效果优于Thread.yield。从时效性上看比Thread.sleepThread.onSpinWait();好很多}//成功表示获取到锁if(LOCKED.compareAndSet(this,false,true)){return;}}}@Overridepublicvoidunlock(){LOCKED.setVolatile(this,false);}}为了灵活性和统一性,我们使用两个锁的句柄,而不是volatile变量或AtomicBoolean,因为我们将有volatile更新、普通读和Atomic更新;如果读者不习惯,可以使用AtomicBOoolean近似替换接下来我们使用JMH来测试这两种锁的性能和有效性。我们使用多线程对一个int类型的变量进行500万次加法,使用我们实现的两个锁来保证并发安全。检查耗时。//测试指标为单次调用时间@BenchmarkMode(Mode.SingleShotTime)//需要预热排除jit实时编译和JVM采集各种指标的影响。由于我们单循环多次,所以预热一次就够了@Warmup(iterations=1)//单线程就够了@Fork(1)//测试次数,我们测试10次@Measurement(iterations=10)//定义一个类实例的生命周期,所有测试线程共享一个Example@State(value=Scope.Benchmark)publicclassTest{privatestaticclassValueHolder{intcount=0;}//测试不同线程数@Param(value={"1","2","5","10","20","50","100"})privateintthreadsCount;@BenchmarkpublicvoidtestTASLock(Blackholeblackhole)throwsInterruptedException{test(newTASLock());}@BenchmarkpublicvoidtestTTASLock(Blackholeblackhole)throwsInterruptedException{test(newTTASLock());}privatevoidtest(Locklock)throwsInterruptedException{ValueHoldervalueHolder=newValueHolder();线程[]线程=新线程[threadsCount];//测试累计5000000次for(inti=0;i{for(intj=0;j<5000000/threads.length;j++){lock.lock();try{valueHolder.count++;}最后{lock.unlock();}}});线程[i].start();}for(inti=0;i