-
前言在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说——你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓——那样就很尴尬了。不过又转念一想,我辈年轻人自当有一种一往无前的锐气,标题大气一点岂不更好?并且大家都是文明人,总归更多的是理解与补充而不是侮辱与谩骂?所以最终还是厚颜用了这么一个不怎么有耻的标题。好了,接下来进入正题,谈谈我对AIDL的理解和认识。正文1,概述AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。是的,首先我们知道的第一点就是:AIDL是一种语言。既然是一种语言,那么相应的就很自然的衍生出了一些问题:为什么要设计出这么一门语言?它有哪些语法?我们应该如何使用它?再深入一点,我们可以思考,我们是如何通过它来达到我们的目的的?更深入一点,为什么要这么设计这门语言?会不会有更好的方式来实现我们的目的?接下来,我们就一步步的来解答上面的这些问题。ps:1,在研究AIDL相关的东西之前,一些必要的知识储备是要有的。一方面是关于Android中service相关的知识,要了解的比较通透才行,关于这方面的东西可以参考 Android中的Service:默默的奉献者 (1),Android中的Service:Binder,Messenger,AIDL(2) 这两篇博文。另一方面是关于Android中序列化的相关知识,这方面的东西文中会简单提及,但是如果想要深入的研究一下的话最好还是去找一些这方面的资料看一下。 2,我的编译环境为Android Studio2.1.2,SDK Version 23,JDK 1.7。2,为什么要设计这门语言?设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。相对于它们而言,我们就好像造物主一样,我们可以通过AIDL来制定一些规则,规定它们能进行哪些交流——比如,它们可以在我们制定的规则下传输一些特定规格的数据。总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用——这种时候就需要使用 AIDL 了。如果想要了解它们更详细的区别的话,可以去我的另一篇博文看看:Android中的Service:Binder,Messenger,AIDL(2)3,它有哪些语法?其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别——毕竟它只是被创造出来简化Android程序员工作的,太复杂不好——所以在这里我就着重的说一下它和 Java 不一样的地方。主要有下面这些点:文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下——在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.lypeer.aidldemo 包下 ,现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.lypeer.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。默认支持的数据类型包括:Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。String 类型。CharSequence类型。List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag 是这样的:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。具体的分析大家可以移步我的另一篇博文:你真的理解AIDL中的in,out,inout么?另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。
-
请问并行操作,线程数量有上限么?
-
【功能模块】gdb调试【操作步骤&问题现象】1、gdb启动调试运行时,打印警告warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.2、应用起来后,之前设置的断点没有生效,无法进入预设的断点gdb升级版本到11.2也不能解决这个问题【截图信息】gdb --versionGNU gdb (GDB) 11.2Copyright (C) 2022 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.gcc --versiongcc (GCC) 7.3.0Copyright (C) 2017 Free Software Foundation, Inc.本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;包括没有适销性和某一专用目的下的适用性担保。【日志信息】(可选,上传日志内容或者附件)
-
本文分享自华为云社区《[Java中提供了synchronized,为什么还要提供Lock呢?](https://bbs.huaweicloud.com/blogs/336889?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者: 冰 河。 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。 # 再造轮子? 既然JVM中提供了synchronized关键字来保证只有一个线程能够访问同步代码块,为何还要提供Lock接口呢?这是在重复造轮子吗?Java的设计者们为何要这样做呢?让我们一起带着疑问往下看。 # 为何提供Lock接口? 很多小伙伴可能会听说过,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢? 如果我们向更深层次思考的话,就不难想到了:我们使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。 # 死锁问题 如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。  - **互斥条件** 在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。 - **不可剥夺条件** 线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。 - **请求与保持条件** 线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。 - **循环等待条件** 在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。 # synchronized的局限性 如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。 然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。 如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢? # 解决问题 了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。  (1)能够响应中断。 synchronized的问题是, 持有锁A后, 如果尝试获取锁B失败, 那么线程就进入阻塞状态, 一旦发生死锁, 就没有任何机会来唤醒阻塞的线程。 但如果阻塞状态的线程能够响应中断信号, 也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它, 那它就有机会释放曾经持有的锁A。 这样就破坏了不可剥夺条件了。 (2)支持超时。 如果线程在一段时间之内没有获取到锁, 不是进入阻塞状态, 而是返回一个错误, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。 (3)非阻塞地获取锁。 如果尝试获取锁失败, 并不进入阻塞状态, 而是直接返回, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。 体现在Lock接口上,就是Lock接口提供的三个方法,如下所示。 // 支持中断的API void lockInterruptibly() throws InterruptedException; // 支持超时的API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支持非阻塞获取锁的API boolean tryLock(); - lockInterruptibly() 支持中断。 - tryLock()方法 tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 - tryLock(long time, TimeUnit unit)方法 tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。 也就是说,对于死锁问题,Lock能够破坏不可剥夺的条件,例如,我们下面的程序代码就破坏了死锁的不可剥夺的条件。 public class TansferAccount{ private Lock thisLock = new ReentrantLock(); private Lock targetLock = new ReentrantLock(); //账户的余额 private Integer balance; //转账操作 public void transfer(TansferAccount target, Integer transferMoney){ boolean isThisLock = thisLock.tryLock(); if(isThisLock){ try{ boolean isTargetLock = targetLock.tryLock(); if(isTargetLock){ try{ if(this.balance >= transferMoney){ this.balance -= transferMoney; target.balance += transferMoney; } }finally{ targetLock.unlock } } }finally{ thisLock.unlock(); } } } } 例外,Lock下面有一个ReentrantLock,而ReentrantLock支持公平锁和非公平锁。 在使用ReentrantLock的时候, ReentrantLock中有两个构造函数, 一个是无参构造函数, 一个是传入fair参数的构造函数。 fair参数代表的是锁的公平策略, 如果传入true就表示需要构造一个公平锁, 反之则表示要构造一个非公平锁。如下代码片段所示。 //无参构造函数: 默认非公平锁 public ReentrantLock() { sync = new NonfairSync(); } //根据公平策略参数创建锁 public ReentrantLock(boolean fair){ sync = fair ? new FairSync() : new NonfairSync(); } 锁的实现在本质上都对应着一个入口等待队列, 如果一个线程没有获得锁, 就会进入等待队列, 当有线程释放锁的时候, 就需要从等待队列中唤醒一个等待的线程。 如果是公平锁, 唤醒的策略就是谁等待的时间长, 就唤醒谁, 很公平; 如果是非公平锁, 则不提供这个公平保证, 有可能等待时间短的线程反而先被唤醒。 而Lock是支持公平锁的,synchronized不支持公平锁。 最后,值得注意的是,在使用Lock加锁时,一定要在finally{}代码块中释放锁,例如,下面的代码片段所示。 try{ lock.lock(); }finally{ lock.unlock(); } 注:其他synchronized和Lock的详细说明,小伙伴们自行查阅即可。
-
WALWAL(Write-Ahead-Log)是HBase的RegionServer在处理数据插入和删除的过程中用来记录操作内容的一种日志。大致过程如下图所示,首先客户端启动一个操作来修改数据,每一个修改都封装到KeyValue对象实例中,并通过RPC调用发送到含有匹配Region的HRegionServer。一旦KeyValue到达,它们就会被发送管理相应行的HRegion实例。数据被写到WAL,然后被放入到实际拥有记录的存储文件的MemStore中。同时还会检查MemStore是否满了,如果满了就会被刷写到磁盘中去。Hlog上图表示了3个不同的region,每一个负责一段rowkey的范围。这些region将共享同一个HLog实例,从不同region来的数据写入WAL的顺序是不确定的。HLogKeyWAL使用Hadoop的SequenceFile,它将记录存储为key/values 的数据集。对于WAL,key是一个HLogKey的实例。KeyValue包括row, column family, qualifier, timestamp, value以及Key Type,Key Type可以标识一个“put”或“delete”操作。wal调用链源码分析基本调用过程如下1、首先client端先把put/delete等api操作封装成List<MutationProto>,然后使用protobuf协议使用rpc服务发送到对应的HRegionServer,HRegionServer调用execRegionServerService()方法解析发送过来的protobuf协议二进制包,通过serviceName找到相应的service并调用callMethod方法执行:2、put/delet等“写”操作会使用MultiRowMutationService这个service来作用,在service中将会调用mutateRows()方法去处理List<MutationProto>,真正调用mutateRows()的是MultiRowMutationService的一个实现类MultiRowMutationEndpoint,MultiRowMutationEndpoint类实现了hbase的行事务。从MultiRowMutationEndpoint类文档可以看出其主要作用:3、在HRegion类中mutateRowsWithLocks方法查看有没执行器(RowProcessor),如果没有则创建一个再调用processRowsWithLocks()方法。processRowsWithLocks方法是整个“写”操作最核心的方法:把写wal,刷wal以及写memstore流程都在这里流转。在这里包括异常处理一共有12步之多。原型如下:在HRegion类中重写。写WAL日志关键步骤如下:3、doWALAppend()这里HRegion会把封装好的WALEdit使用FSHLog的append方法追加到日志文件,但是由于文件本身在内存中有缓存的原因,还需要调用sync刷入磁盘。这里只是把WALEdit数据放到一个LMAX Disrutpor RingBuffer中。这个RingBuffer是一个线程安全的消息队列,在wal中主要用于有效且安全的协调多个生产者一个消费者模型。其中多个生产者就是这个append方法,将会有很多client产生数据都放到这个消息队列中,但是只有一个消费者从这个队列中取数据并调用sync方法把数据从缓存刷到磁盘,这样能保证WAL日志并发写入时日志的全局唯一顺序。在这步中会调用sync()方法,除了isMetaRegion,sync()将根据client设置的持久化等级选择是否调用wal(FSHLog)的sync方法如代码中所示当前sync_wal和fsync_wal采用的是同一策略都是:调用HFLog的sync()方法。sync()是一个阻塞方法,需要等到数据真正的刷到磁盘后,便会唤醒它,然后工作线程返回写入memstore,完成一次“写”操作。Hbase WAL 线程模型源码分析其线程模型主要实现实在FSHLog中,FSHLog是WAL接口的实现类,实现了最关键的apend()和sync()方法,其模型如图所示:这个图主要描述了HRegion中调用append和sync后,hbase的wal线程流转模型。最左边是有多个client提交到HRegion的append和sync操作。当调用append后WALEdit和WALKey会被封装成FSWALEntry类进而再封装成RingBufferTruck类放入一个线程安全的Buffer(LMAX Disruptor RingBuffer)中。当调用sync后会生成一个SyncFuture进而封装成RingBufferTruck类同样放入这个Buffer中,然后工作线程此时会被阻塞等待被notify()唤醒。在最右边会有一个且只有一个线程专门去处理这些RingBufferTruck,如果是FSWALEntry则写入hadoop sequence文件。因为文件缓存的存在,这时候很可能client数据并没有落盘。所以进一步如果是SyncFuture会被批量的放到一个线程池中,异步的批量去刷盘,刷盘成功后唤醒工作线程完成wal。1、工作线程(RingBuffer生成者)调用过程如下:FSHLog的append方法首先会从LAMX Disruptor RingbBuffer中拿到一个序号作为txid(sequence),然后把WALEdit,WALKey和sequence等构建一个FSALEntry实例entry,并把entry放到ringbuffer中。而entry以truck(RingBufferTruck,ringbuffer实际存储类型)通过sequence和ringbuffer一一对应。如果client设置的持久化等级是USER_DEFAULT,SYNC_WAL或FSYNC_WAL,那么工作线程的HRegion还将调用FSHLog的sync()方法。追踪代码可以分析出Sync()方法会往ringbuffer中放入一个SyncFuture对象,并阻塞等待完成(唤醒)。blockOnSync()方法会阻塞工作线程。2、RingBuffer消费者调用过程如下:在FSHLog中有一个私有内部类RingBufferEventHandler类实现了LAMX Disruptor的EventHandler接口,也即是实现了OnEvent方法的ringbuffer的消费者。Disruptor通过 java.util.concurrent.ExecutorService 提供的线程来触发 Consumer 的事件处理,可以看到hbase的wal中只启了一个线程,从源码注释中也可以看到RingBufferEventHandler在运行中只有单个线程。由于消费者是按照sequence的顺序刷数据,这样就能保证WAL日志并发写入时只有一个线程在真正的写入日志文件的可感知的全局唯一顺序。RingBufferEventHandler类的onEvent()(一个回调方法)是具体处理append和sync的方法。在前面说明过wal使用RingBufferTruck来封装WALEntry和SyncFuture(如下图源码),在消费线程的实际执行方法onEvent()中就是被ringbuffer通知一个个的从ringbfer取出RingBufferTruck,如果是WALEntry则使用当前HadoopSequence文件writer写入文件(此时很可能写的是文件缓存),如果是SyncFuture则简单的轮询处理放入SyncRunner线程异步去把文件缓存中数据刷到磁盘。相关代码如下:SyncRunner是一个线程,wal实际有一个SyncRunner的线程组,专门负责之前append到文件缓存的刷盘工作。SyncRunner的线程方法(run())负责具体的刷写文件缓存到磁盘的工作。首先去之前提交的synceFutues中拿到其中sequence最大的SyncFuture实例,并拿到它对应ringbuffer的sequence。再去比对当前最大的sequence,如果发现比当前最大的sequence则去调用releaseSyncFuture()方法释放synceFuture,实际就是notify通知正被阻塞的sync操作,让工作线程可以继续往下继续。SyncRunner的run方法核心调用如下:刷磁盘是很费时的操作,如果每次都同步的去刷client的回应比较快,但是写效率不高,如果异步刷文件缓存,写效率提高但是友好性降低,在考虑了写吞吐率和对client友好回应平衡后,wal选择了后者,积累了一定量(通过ringbuffer的sequence)的缓存再刷磁盘以此提高写效率和吞吐率。这个决策从hbase存储机制最初采用lsm树把随机写转换成顺序写以提高写吞吐率,可以看出是目标一致的。 这样整个wal写入也完成了。
-
以下两种方式二选一即可:自动化配置工具:cid:link_0手动配置步骤:1、以root用户登录OMS主节点切换至tmp路径下su - rootcd /tmp2、在新建目录下创建core.sh脚本,内容如下:sed -i '/^.*hard.*core.*$/d' /etc/security/limits.confsed -i '/^.*soft.*core.*$/d' /etc/security/limits.confecho "* soft core unlimited" >> /etc/security/limits.confecho "* hard core unlimited" >> /etc/security/limits.confsed -i "/fs.suid_dumpable/d" /etc/sysctl.confecho "core" > /proc/sys/kernel/core_patternsed -i '/^kernel.core_pattern.*$/d' /etc/sysctl.confecho "kernel.core_pattern=core" >> /etc/sysctl.confecho "fs.suid_dumpable = 1" >> /etc/sysctl.confecho "kernel.core_uses_pid=0" >> /etc/sysctl.conf/sbin/sysctl -p3、在新集群OMS主节点借助批量执行工具,配置集群所有节点core,用root执行如下命令即可分开命令执行core配置dos2unix /tmp/core.shcd /opt/FusionInsight_SetupTool/preinstall/tools/cluster./clusterscp.sh put /tmp/core.sh /tmp/./clustercmd.sh "sh /tmp/core.sh" 4、kill om_monitor重启集群后检查core配置是否生效gs_ssh -c "killall -u omm om_monitor"检查配置是否生效:1) ps -ef|grep masterx,找到进程号2) cd /proc/进程号。3) view limits4) cat /proc/sys/kernel/core_pattern5、 验证是否成功kill -11 从备DN进程号,查看对应实例目录下是否有core文件产生。
-
MDS想要同时运行客户端client和服务端server的进程,一个是发送数据,一个是接受数据,如何同时运行观察运行结果呢
-
现象描述Ceph集群测试读写性能,进行一轮测试后。重启集群OSD节点,继续开展读写测试,测试工具报错:java.lang.RuntimeException: Slave hd2-0 prematurely terminated.at Vdb.common.failure[common.java:335)at Vdb.SlaveStarter.startSlave(SlaveStarter.java:198)at Vdb.SlaveStarter.run(SlaveStarter.java:47)具体信息如下图所示。查看Ceph集群状态发现部分OSD状态为down,如图所示。处理步骤查看Ceph日志时发现往Ceph分配内存时失败,怀疑在OSD进程在获取内存是有异常。输入如图命令发现“osd_memory_target”的值并非官方发布的默认的4G。在ceph.conf文件中添加“osd_memory_target = 4294967296” ,使分配给每个osd的内存限制为4GB。将修改后的文件推送到其他节点。ceph-deploy --overwrite-conf admin ceph1 ceph2 ceph3 client1 client2 client3重启集群。systemctl restart ceph.target
-
面向对象三大特性:继承,封装,多态封装3中修饰符:public,private,protected,给位于同一个或不同包中的对象赋予了不同的访问权限封装的一些好处通过隐藏对象的属性来保护对象内部的状态提高代码的可用性,可维护性提高模块化继承给对象提供从基类获取字段和方法的能力,基础提高代码的重用性,可以在不修改类的情况下添加新的特性多态多态就是同一函数在不同类中有不同的实现;面向对象的多态性,即“一个接口,多个方法”。多态性体现在基类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。final,finally,finalize的区别finalfinal可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个 常量不能被重新赋值。finallyfinally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放入finally代码块中,表示不管是 否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。finalizefinalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调 用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。int和integer的区别intint是基本数据类型integerinteger是其包装类,是一个类为了在各种类型中转换,通过各种方法调用int a = 0; String result = Integer.toString(a); //将int转换为String重载与重写的区别override重写overload重载抽象类和接口的区别接口时公开的,不能有私有的方法和变量,抽象类可以有私有的方法或私有的变量抽象类和接口的区别反射的用途以及实现反射机制所提供的功能在运行时创造一个类的对象;判断一个类所具有的成员变量和方法调用一个对象的方法生成动态代理Java反射的主要功能:确定一个对象的类取出类的modifiers,数据成员,方法,构造类,超类在运行时刻调用动态对象的方法.创建数组,数组大小和类型反射机制的理解和应用自定义注解的场景及实现登陆、权限拦截、日志处理,以及各种Java 框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志 注解,就进行日志记录。http请求的get和post方式的区别原理区别一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。根据HTTP规范,POST可能会修改服务器上的资源的请求。比如CSDN的博客,用户提交一篇文章或者一个读者提交评论是通过POST请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。请求的区别GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在 客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。参考:get和post请求方式的区别seesion与cookie的区别cookie数据存放在客户的浏览器上,session数据放在服务器上.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。设置cookie时间可以使cookie过期。但是使用session-destory(),我们将会销毁会话。session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)JDBC流程加载JDBC驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String className)实现。例如:try{ //加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver") ; }catch(ClassNotFoundException e){ System.out.println("找不到驱动程序类 ,加载驱动失败!"); e.printStackTrace() ; 成功加载后,会将Driver类的实例注册到DriverManager类中。提供JDBC连接的URL连接URL定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识协议:在JDBC中总是以jdbc开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。例如:jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的连接URL)表示使用Unicode字符集。如果characterEncoding设置为 gb2312或GBK,本参数必须设置为true 。characterEncoding=gbk:字符编码方式。创建数据库的连接java.sql.DriverManager Connection代表一个数据库的连接。使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。例如: //连接MySql数据库,用户名和密码都是rootString url = "jdbc:mysql://localhost:3306/test" ; String username = "root" ; String password = "root" ; try{ Connection con = DriverManager.getConnection(url , username , password ) ; }catch(SQLException se){ System.out.println("数据库连接失败!"); se.printStackTrace() ; • 要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:1、执行静态SQL语句。通常通过Statement实例实现。2、执行动态SQL语句。通常通过PreparedStatement实例实现。3、执行数据库存储过程。通常通过CallableStatement实例实现。具体的实现方式:Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;执行SQL语句Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows =stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;处理结果两种情况:1、执行更新返回的是本次操作影响到的记录数。2、执行查询返回的结果是一个ResultSet对象。• ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。• 使用结果集(ResultSet)对象的访问方法获取数据:while(rs.next()){String name = rs.getString(“name”) ;String pass = rs.getString(1) ; // 此方法比较高效}(列是从左到右编号的,并且从列1开始)关闭JDBC对象操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:1、关闭记录集2、关闭声明3、关闭连接对象 if(rs!=null){ // 关闭记录集 try{ rs.close(); }catch(SQLException e){e.printStackTrace(); } }try{ stmt.close(); } }catch(SQLException e){e.printStackTrace(); } if(conn!=null){ // 关闭连接对象 try{ conn.close(); }catch(SQLException e){ e.printStackTrace();20} }MVC思想M:Model 模型V:View 视图C:Controller控制器模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中通常是 用Servlet来实现,框架的话很多是用Struts2来实现这一层),视图就主要是你看到的,比如JSP 等.当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去 把结果输出到视图层,这里可能是进行重定向或转发等.equals 与 == 的区别值类型(int,char,long,boolean等)都是用==判断相等性。对象引用的话,判断引用所指的对象 是否是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判 断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并不是同一个(和jvm实现方式有关),因此用 ==判断他们可能不相等,但用equals判断一定是相等的。线程创建线程的方法及实现Java中创建线程主要有三种方式:一、继承Thread类创建线程类(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2) 创建Thread子类的实例,即创建了线程对象。(3) 调用线程对象的start()方法来启动该线程。package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方法体就是现场执行体public void run() { for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i< 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i): if(i==20) { new FirstThreadTest().start(); new FirstThreadTest().start(); } } }二、通过Runnable接口创建线程类(1) 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2) 创建 Runnable实现类的实例的target来创建Thread对象,该Thread对象才是真正的线程对象。(3)调用线程对象的start()方法来启动该线程package com.thread; public class RunnableThreadTest implements Runnable { private int i; public void run() { for(i = 0;i <100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新线程1").start(); new Thread(rtt,"新线程2").start(); } } } } 三、通过Callable和Future创建线程(1) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。(2) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(3) 使用FutureTask对象作为Thread对象的target创建并启动新线程。(4) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); 13 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循 环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception 34 { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }采用实现Runnable、Callable接口的方式创见多线程时,优势是:线程类只是实现了 接口或 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同 一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面 向对象的思想。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。使用继承Thread类的方式创建多线程时优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this 即可获得当前线程。劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。sleep() 、join()、yield()的区别一、sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度 程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如 果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。二、join()Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前, B不能工作。保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有 存活,则当前线程不需要停止。三、yield()yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方 法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。再给大家推荐一本书:Java并发编程想要电子版也可以私信我线程池的几种方式newFixedThreadPool(int nThreads)创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数 量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程newCachedThreadPool()创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当 需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制newSingleThreadExecutor()这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会 创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行newScheduledThreadPool(int corePoolSize)创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。private static final Executor exec=Executors.newFixedThreadPool(50); Runnable runnable=new Runnable(){ public void run(){ ... } } exec.execute(runnable); Callable<Object> callable=new Callable<Object>() { public Object call() throws Exception { return null; } } Future future=executorService.submit(callable); future.get(); // 等待计算完成后,获取结果 future.isDone(); // 如果任务已完成,则返回 true future.isCancelled(); // 如果在任务正常完成前将其取消,则返回 true future.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false 允许正在运行的任务运行完成线程的生命周期新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态(1)生命周期的五种状态新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队 等候得到CPU资源。例如:t1.start();运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有 优先级更高的线程进入,线程将一直运行到结束。死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态 等待执行。自然终止:正常运行run()方法后终止异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用wait()方法。(调用motify()方法回到就绪状态)被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)锁机制线程安全线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲 突。在Java里,线程安全一般体现在两个方面:1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关 键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element, 问题就出现了。2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。volatle实现原理深入分析 Volatile 的实现原理悲观锁,乐观锁是一种思想。可以用在很多方面。比如数据库方面。悲观锁就是for update(锁定查询的行)乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读比较写的操作。)JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现)本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出 当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重 复读比较写的操作。CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。乐观锁的业务场景及实现方式每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁, 但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不 进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁, 期间该数据可以被其他线程进行读写操作。乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可 能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量 的查询操作,降低了系统的吞吐量。最后都看到这了,给孩子一个三连支持一下吧,Java对初学者很友好;Java资源丰富,因为它可以解决不同的问题;Java有一个庞大而友好的社区;Java无处不在,因此更容易找到第一份工作;Java开发人员缺口大,薪水很高。只要你具备能力,薪资待遇你敢要公司就敢给。链接:https://bbs.huaweicloud.com/blogs/336625
-
1.1 介绍QUANTUM ESPRESSO是一种用于电子结构计算和材料建模的abinitio量子化学方法的软件套件,在GNU通用公共许可证下免费分发。它基于密度泛函理论,平面波基组和赝势(包括范数守恒和超软)。该程序主要在Fortran-90中编写,其中一些部分在C或Fortran-77中,是根据不同的独立开发的核心软件包的合并和重新设计以及一组旨在互操作的软件包构建的。基本软件包包括Pwscf,它解决了自洽的Kohn和Sham方程,获得了周期性实体,CP实现了Car-Parrinello分子动力学,PostProc实现了数据分析和绘图。关于附加封装,值得注意的是指出赝势生成的原子,PHonon封装,它实现了密度泛函扰动理论(DFPT),用于计算能量相对于原子位移和NEB的二阶和三阶导数。关于QUANTUM ESPRESSO的更多信息请访问QUANTUM ESPRESSO官网。语言:C++/Fortran。一句话描述:可扩展的计算化学工具。开源协议:GPL 2.0。建议的版本建议使用的版本为“QE 6.8”。1.2 环境要求硬件要求硬件要求如表2-1所示。硬件要求项目说明CPU鲲鹏920处理器。GPUNVIDIA Tesla V100。 软件要求软件要求如表2-2所示。软件要求项目版本下载地址QUANTUM ESPRESSO6.8https://github.com/QEF/q-e/archive/refs/tags/qe-6.8.tar.gzOpenBLAS0.3.6https://github.com/xianyi/OpenBLAS/archive/refs/tags/v0.3.6.tar.gzHyper MPI1.1.1https://support.huaweicloud.com/usermanual-kunpenghpcs/userg_huaweimpi_0010.htmlNVIDIA CUDA11.4https://developer.download.nvidia.com/compute/cuda/11.4.0/local_installers/cuda_11.4.0_470.42.01_linux_sbsa.runNVIDIA HPC SDK21.9https://developer.download.nvidia.com/hpc-sdk/21.9/nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz 操作系统要求操作系统要求如表2-3所示。操作系统要求项目版本下载地址CentOS8.2https://vault.centos.org/8.2.2004/isos/aarch64/CentOS-8.2.2004-aarch64-dvd1.isoKernel4.18.0-193https://vault.centos.org/8.2.2004/BaseOS/Source/SPackages/kernel-4.18.0-193.el8.src.rpm 1.3 移植规划数据本章节给出QE软件在移植过程中涉及到的相关软件安装规划路径的用途及详细说明。移植规划数据序号软件安装规划路径用途说明1-基础环境搭建中的各安装包安装路径。参考《HPC解决方案 基础环境搭建指导书》中“安装规划数据”章 节。2/path/to/qeQUANTUM ESPRESSO的安装规划路径。这里的安装规划路径只是一个举例说明,建议部署在共享路径中。现网需要根据实际情况调整,后续章节凡是遇到安装路径的命令,都以现网实际规划的安装路径为准进行替换,不再单独说明。3/usr/local/cuda-11.4NVIDIA CUDA的安装规划路径。4/path/to/openblasOpenBLAS的安装规划路径。5/opt/nvidia/hpc_sdkNVIDIA HPC SDK的安装规划路径。6/path/to/CASEQUANTUM ESPRESSO的算例文件存放规划路径。 1.4 配置编译环境前提条件使用SFTP工具将各安装包上传至服务器对应目录下。配置流程配置流程序号配置项说明1基础环境搭建参考《HPC解决方案 基础环境搭建指导书》中“集群场景环境搭建”章节。2禁用nouveau驱动参考2.4.1 禁用nouveau驱动。3安装NVIDIA CUDA参考2.4.2 安装NVIDIA CUDA组件。4安装Hyper MPI参考2.4.3 安装Hyper MPI。5安装NVIDIA HPC SDK参考2.4.4 安装NVIDIA HPC SDK。6安装OpenBLAS参考2.4.5 安装OpenBLAS。 1.4.1 禁用nouveau驱动操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令查看nouveau驱动是否已禁用。 lsmod |grep nouveau若有回显信息,表示nouveau驱动未禁用,则执行步骤3。若无回显信息,表示nouveau驱动已禁用,则结束操作。步骤 3 执行以下命令禁用nouveau驱动。新建文件“disable-nouveau.conf”。 vi /etc/modprobe.d/disable-nouveau.conf按“i”进入编辑模式,添加如下内容。 blacklist nouveau options nouveau modeset=0按“Esc”键,输入:wq!,按“Enter”保存并退出编辑。步骤 4 执行以下命令备份并生成新的“initramfs”文件 cp /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r)-nouveau.img dracut -f /boot/initramfs-$(uname -r).img $(uname -r)步骤 5 执行以下命令重启机器。 reboot----结束1.4.2 安装NVIDIA CUDA组件操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令确认nouveau驱动是否已禁用。 lsmod |grep nouveau若有回显信息,表示nouveau驱动未禁用,则执行步骤3~步骤5。若无回显信息,表示nouveau驱动已禁用,则结束操作。步骤 3 执行以下命令安装NVIDIA CUDA组件。 wget https://developer.download.nvidia.com/compute/cuda/11.4.0/local_installers/cuda_11.4.0_470.42.01_linux_sbsa.run sudo shcuda_11.4.0_470.42.01_linux_sbsa.run步骤 4 执行以下命令配置环境变量。 export PATH=/usr/local/cuda-11.4/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.4/lib64:$LD_LIBRARY_PATH步骤 5 执行以下命令进行验证。 nvcc --version若返回结果已包含NVIDIA CUDA组件版本信息,说明安装成功。----结束1.4.3 安装Hyper MPI操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令安装HUCX源码包和XUCG源码包。 wget -c https://github.com/kunpengcompute/hucx/archive/refs/heads/huawei.zip -O hucx-huawei.zip wget -c https://github.com/kunpengcompute/xucg/archive/refs/heads/huawei.zip -O xucg-huawei.zip步骤 3 执行以下命令解压HUCX源码包和XUCG源码包。 unzip hucx-huawei.zip unzip xucg-huawei.zip步骤 4 执行以下命令将XUCG源码包中的内容复制到HUCX源码包中的“src/ucg”目录下。 cp -r xucg-huawei/* hucx-huawei/src/ucg/步骤 5 执行以下命令进入hucx-huawei目录。 cd hucx-huawei步骤 6 执行以下命令设置环境变量。 export SEC_CFLAGS="-Wno-error=deprecated-declarations -DHAVE___CLEAR_CACHE=1 -Wno-error=asm-operand-widths -Wno-error=implicit-int-float-conversion -Wno-error=format -Wno-error=tautological-pointer-compare"步骤 7 执行以下命令创建HUCX安装目录。 mkdir -p /path/to/ucx步骤 8 执行以下命令安装HUCX。 ./autogen.sh ./contrib/configure-opt --prefix=/path/to/ucx CFLAGS="$SEC_CFLAGS" CXXFLAGS="$SEC_CFLAGS" make -j32 make install步骤 9 执行以下命令获取Hyper MPI源码包。 wget -c https://github.com/kunpengcompute/hmpi/archive/refs/heads/huawei.zip -O hmpi-huawei.zip步骤 10 执行以下命令解压Hyper MPI源码包。 unzip hmpi-huawei.zip步骤 11 执行以下命令进入hmpi-huawei目录。 cd hmpi-huawei步骤 12 执行以下命令安装Hyper MPI。 ./autogen.pl ./configure --prefix=/path/to/ompi --with-platform=contrib/platform/mellanox/optimized --enable-mpi1-compatibility --with-ucx=/path/to/ucx make -j16 make install步骤 13 执行以下命令配置环境变量。 export PATH=/path/to/ompi/bin:/path/to/ucx/bin:$PATH export LD_LIBRARY_PATH=/path/to/ompi/lib:/path/to/ucx/lib:$LD_LIBRARY_PATH export OPAL_PREFIX=/path/to/ompi/ export INCLUDE=/path/to/ompi:$INCLUDE----结束1.4.4 安装NVIDIA HPC SDK操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令获取毕昇编译器软件包。 wget https://developer.download.nvidia.com/hpc-sdk/21.9/nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz步骤 3 执行以下命令创建毕昇编译器安装目录(这里以/opt/compiler为例)。 mkdir -p /opt/compiler步骤 4 执行以下命令将毕昇编译器压缩包拷贝到安装目录下。 cp –r nvhpc_2021_219_Linux_aarch64_cuda_11.4 /opt/compiler步骤 5 执行以下命令进入压缩包目录。 cd /opt/compiler步骤 6 执行以下命令解压缩软件包。 tar -zxvf nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz步骤 7 执行以下命令解压目录。 cd nvhpc_2021_219_Linux_aarch64_cuda_11.4/步骤 8 执行以下命令进行安装。 ./install按“Enter”键确认安装位置,默认为“/opt/nvidia/hpc_sdk”,再按“1”选择单机安装,安装程序会自行找到之前安装的“cuda toolkit”,安装完成后按“Enter”键结束。步骤 9 执行以下命令配置NVIDIA HPC SDK的环境变量。 module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/21.9步骤 10 执行以下命令进行验证。 pgcc --version若返回结果已包含“PGI Compilers and Tools”字样,说明安装成功。----结束1.4.5 安装OpenBLAS操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令解压OpenBLAS安装包。 tar -zxvf OpenBLAS-0.3.6.tar.gz步骤 3 执行以下命令进入解压后的目录。 cd OpenBLAS-0.3.6步骤 4 执行以下命令编译安装OpenBLAS。 make make PREFIX=/path/to/OPENBLAS install步骤 5 执行以下命令设置OpenBLAS的环境变量。 export LD_LIBRARY_PATH=/path/to/OPENBLAS/lib:$LD_LIBRARY_PATH----结束1.5 获取源码操作步骤步骤 1 下载QUANTUM ESPRESSO安装包。下载地址:https://github.com/QEF/q-e/releases/tag/qe-6.8步骤 2 使用SFTP工具将QUANTUM ESPRESSO安装包上传至服务器“/path/to/qe”目录。----结束1.6 编译和安装操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入主程序安装目录。 cd /path/to/qe步骤 3 执行以下命令解压安装包。 tar -zxvf q-e-qe-6.8.tar步骤 4 执行以下命令进入解压后路径。 cd q-e-qe-6.8/步骤 5 执行以下命令开始编译。 ./configure --with-cuda=yes --with-cuda-runtime=11.4 --with-cuda-cc=80 --enable-openmp --with-scalapack=no“--with-cuda-cc=80”请根据当前机器的显卡架构进行修改,此处测试机器显卡是v100。 make -j32 pwall若编译机器无法联网,需下载“devicexlib-master.tar.gz”包,并上传至编译机器,编译完成后执行make pp -j32,具体命令如下: mv devicexlib-master.tar.gz /path/to/qe-gpu-6.7/external devicexlib.tar.gz tar xzf devicexlib.tar.gz --strip-components=1 export F90FLAGS="-FAST -mCACHE_align -Mpreprocess -Mlarge_arrays" ./configure FC=pgf90 CC=pgcc --with-cuda=no --with-cuda-cc= --with-cuda-runtime= --disable-parallel --enable-cuda-env-check=no make pp -j32若出现类似如下错误:是由于链接库配置不对,需要更改“inc”文件。 打开“inc”文件。 vi make.inc按“i”进入编辑模式,在如下位置增加“LD_LIBS = -lcurand”。按“Esc”键,输入:wq!,按“Enter”保存并退出编辑。步骤 6 执行以下命令查看是否生成可执行文件。 ll /path/to/qe-gpu-6.7/bin/pw.x回显信息中包含“pw.x”文件,表示已生成可执行文件。步骤 7 执行以下命令设置环境变量。 export PATH=/path/to/qe/bin/:$PATH----结束1.7 运行和验证操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入测试目录。 cd /path/to/CASE步骤 3 执行以下命令下载测试算例,使用SFTP工具将测试算例上传至服务器“/path/to/CASE”目录。 wget https://github.com/QEF/benchmarks/archive/refs/tags/bench0.0.tar.gz步骤 4 执行以下命令解压测试算例包,并将需要的文件拷贝到“/path/to/CASE”目录。 tar xvf benchmarks-bench0.0.tar.gz cp benchmarks-master/AUSURF112/* ./步骤 5 执行以下命令运行QE程序。 mpirun --allow-run-as-root -np 2 -x OMP_NUM_THREADS=1 --mca btl ^openib pw.x -input ./ausurf.in 2>&1 | tee -a qe.log“-np”表示测试使用的总进程数,这里写2是测试机器有2张显卡。“-x OMP_NUM_THREADS=1”表示指定每个进程的线程数为1。需要查看“qe.log”日志中的“WALL”数值,单位是“s”,数值越高性能越低,输出的结果如下图所示。----结束
yuanchunlin@汪汪队
发表于2022-03-10 17:41:55
2022-03-10 17:41:55
最后回复
yuanchunlin@汪汪队
2022-03-10 17:41:55
977 0 -
1 介绍fastp是一种工具,旨在为FastQ文件提供快速的多合一预处理。该工具是用C ++开发的,支持多线程以提供高性能。关于fastp的更多信息请访问GitHub相关页面。语言:C/C++一句话描述:超快速的多合一FASTQ预处理器。开源协议:MIT License建议的版本建议使用版本为“fastp 0.20.1”。2 环境要求硬件要求硬件要求如表2-1所示。表2-1 硬件要求项目说明CPUKunpeng 920 软件要求软件要求如表2-2所示。表2-2 软件要求项目版本下载地址fastp0.20.1https://github.com/OpenGene/fastp/archive/v0.20.1.tar.gz测试算例1ERR1044518_1.fastqftp://ftp.sra.ebi.ac.uk/vol1/fastq/ERR104/008/ERR1044518/ERR1044518_1.fastq.gz测试算例2ERR1044518_2.fastqftp://ftp.sra.ebi.ac.uk/vol1/fastq/ERR104/008/ERR1044518/ERR1044518_2.fastq.gz 操作系统要求操作系统要求如表2-3所示。表2-3 操作系统要求项目版本下载地址CentOS7.6https://www.centos.org/download/Kernel4.14.0-115.el7a.0.1https://www.centos.org/download/3 移植规划数据本章节给出fastp软件在移植过程中涉及到的相关软件安装规划路径的用途及详细说明。表3-1 移植规划数据序号软件安装规划路径用途说明1-基础环境搭建中的各安装包安装路径。参考《HPC解决方案 基础环境搭建指南》中“安装规划数据”章节。2/path/to/FASTPfastp的安装规划路径。这里的安装规划路径只是一个举例说明,建议部署在共享路径中。现网需要根据实际情况调整,后续章节凡是遇到安装路径的命令,都以现网实际规划的安装路径为准进行替换,不再单独说明。4 配置编译环境前提条件使用SFTP工具将各安装包上传至服务器对应目录下。配置流程表4-1 配置流程序号配置项说明1基础环境搭建参考《HPC解决方案 基础环境搭建指南》中“集群场景环境搭建”章节。5 获取源码操作步骤步骤 1 下载fastp源码包“fastp-0.20.1.tar.gz”。下载地址:https://github.com/OpenGene/fastp/archive/v0.20.1.tar.gz步骤 2 使用SFTP工具将下载好的软件包上传至服务器“/path/to/FASTP”目录。----结束6 编译和安装操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令,解压fastp安装包。tar -xvf fastp-0.20.1.tar.gz步骤 3 执行以下命令,进入解压后的目录。cd fastp-0.20.1步骤 4 执行以下命令,编译安装。make -j 16 步骤 5 执行以下命令,设置环境变量。export PATH=/path/to/FASTP/fastp-0.20.1:$PATH----结束7 运行和验证操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入算例目录。cd /path/to/FASTP/testdata步骤 3 执行以下命令解压算例文件。gzip -d ERR1044518_1.fastq.gzgzip -d ERR1044518_2.fastq.gz步骤 4 执行以下命令,运行算例。{ time fastp -i ERR1044518_1.fastq -I ERR1044518_2.fastq -w 16 ; } 2>&1 |tee fastp.log运行完成后,将回显以下信息: Duplication rate: 0.581538% Insert size peak (evaluated by paired-end reads): 169 JSON report: fastp.json HTML report: fastp.html说明: fastp支持多线程运行,-w指定线程数,但最多使用16个线程。运行完成后会生成fast.json和fast.html两份报告,可以通过-j和-h参数分别指定两个文件的路径和名称。更多参数信息可以使用fastp --help命令查看。----结束8 更多资源获取更多资源,请访问fastp的GitHub页面:https://github.com/OpenGene/fastp。
-
Runnable VS CallableCallable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Callable 接口可以返回结果或抛出检查异常Runnable 接口不会返回结果或抛出检查异常,如果任务不需要返回结果或抛出异常推荐使用 Runnable接口,这样代码看起来会更加简洁工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))shutdown() VS shutdownNow()shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。 shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终isTerminated() VS isShutdown()isShutDown 当调用 shutdown() 方法后返回为 true。isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
-
Oracle并行查询计划分析Oracle 的并行执行计划是由一个或者几个Data Flow Operator(DFO)组成,同一棵DFO树中,最多只有两组PX进程(生产者和消费者)。每个生产者进程都存在一个和每个消费者进程的连接,每个PX进程和QC都存在一个连接。两组PX进程之间,同一时间只存在一个活跃的数据分发,如果执行路径很长,数据需要多次分发,两组PX进程会变换生产者消费者角色,相互协作,完成所有并行操作PX进程之间或者与QC的连接至少存在一个(单节点下至多三个,RAC环境下至多四个)消息缓冲区用于进程间数据交互,该消息缓冲区默认在Largepool中分配(如果没有配置Largepool则在Sharedpool中分配)。多个缓冲区是为了实现异步通信,提高性能 Oracle并行执行计划树红色为数据消费者,蓝色为数据生产者数据消费者和数据生产之间的数据分发数据生产者和消费者之间数据分发有四种方式:send hashsend broadcastSend replicate(12c特性)adaptive(12 c新特性) send hash:生产者PX进程会根据join key,生成一个hash值,然后根据这个hash值把这行数据发送给一个消费者px进程, Hash分发的本质是把hashjoin的左边和右边(两个数据源),通过同样hash函数重新分发,切 分为N个工作单元(假设DoP=N),再进行join ,目的是减少PX进程进行join 操作时需要连接的数据量,如果hash join有一边在连接键上做hash分区,那么优化器可以选择对分区表不分发,因为hash分区已经对数据完成切分,这只需要hash分发hash join的其中一边,这就是partial partition wise join如果hash join的两边都在连接键上做了hash join分区,那么每个PX进程可以独立的处理对等的hash分区, 没有数据需要分发,这是full partition wise join send broadcast:广播分发是数据生产者将数据广播给所有的数据消费者,也就是说每个消费者都有一份数据,广播分发适合扫描后结果集相对比较小的表,广播分发是,数据结果行数*PX进程数据(消费者),相当于每个PX消费者进程都有一份相同的数据,小表驱动大表,建议做广播分发,如果分发的是大表,分发会消耗大量的CPU和内存Send replicate(12c特性):复制分发其实没有数据分发,每个PX进程重复扫描维度表(inner table),并行扫描事实表(outer table),在进行hash join时,每个PX进程重复扫描hashj oin 的左边的表,buffer cache 被用来缓存hashj oin 左边的小表,减少重复扫描所需的物理读,replicate 仅限于hashj oin 左边是表的情况,如果 hashj oin 的左边的结果集来自其他操作,比如join 或者视图,那么此时无法使用replicateadaptive(12 c新特性):通常, replicate 或者 broadcast 分发不受数据倾斜的影响.对于 hash 分发, hash join 两边连接键的最热门数据, 会被分发到同一 PX 进程进行 join 操作, 容易造成明显的并行执行倾斜.12c 引入 adaptive 分发, 可以解决 hash 分发时并行执行倾斜的问题.如果执行计划显示px send hybrid hash,说明该执行计划启用了adaptive分发特性。对hash join左边分发之前, 会插入一个STATISTICS COLLECTOR操作, 用于运行时确定hash join左边数据集的大小. 如果hash join左边的数据量小于并行度的两倍, 那么对于hash join左边的分发会切换为broadcast方式, 对hash join右边的分发为round-robin. 如果hash join左边的数据大于等于并行度的两倍, 对于hash join两边的分发方式都为hash, 和传统的hash分发一样如果存在柱状图信息, 表明hash join右边连接键上存在数据倾斜, 大部分数据为少数热门的值. 硬解析时, 会对hash join右边的表进行动态采样, 确认热点数据(重复度高的数据),Hash join的左边,的热点的数据会被广播到每个接收者,非热点数据被hash分发;Hash join右边的热点数据通过round-robin的方式发送, 非热点数据被hash分发. 布隆过滤:Oracle的布隆过滤器就是在执行join之前,过滤掉大部分的数据,其实就是包hash join的部分连接操作提前了,对 hash join 右边扫描时, 就第一时间把不符合 join条件的大部分数据过滤掉. 大大降低后续数据分发和 hash join 操作的成本. 不同的分布方式, 布隆过滤的生成和使用方式稍有不同:对于 broadcast 分发和 replicate, 每个 PX 进程持有 hash join 左边的完整数据, 对连接键生成 一个完整的布隆过滤, 扫描 hash join 右边时使用. 如果 sql 涉及多个维度表, 维度表全部使用broadcast 分发, 优化器可能对不同的维度表数据生成多个的布隆过滤, 在扫描事实表时同时使用.对于hash 分发,作为消费者的进程接收了hash join左边的数据之后,每个PX进程分别对各自的数据集生成布隆过滤,在广播给作为生产者的每个PX进程,扫描右边数据使用。Oracle数据库会根据统计信息和SQL的过滤条件自动选择是否布隆过滤。大部分场景,使用布隆过滤都会提升性能,但在某些场景下,不建议使用布隆过滤,例如:hash join左边的结果集比较大,几百万或者几千万的数据,且连接键的重复数据不多,这种情况下使用布隆过滤,生成额布隆过滤会非常大,无法在CPU cache中完整缓存数据,没事使用布隆过滤都需要到内存里读取数据进行判断,会导致性能下降
-
Redis想必大家都或多或少听过吧,我们在工作学习中通常用它来作为缓存使用,既然是作为缓存,大家的第一反应肯定是:这家伙很快!实际上它确实也很快 : ),但Redis底层却是单线程的!有同学可能就要有疑问了,为什么单线程的Redis却能够快到飞起?别急,我尽量用通俗易懂的语言来给各位说道说道~~Redis是单线程,主要是指Redis的网络IO和读写是由一个线程来完成的,但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。这不是本文讨论的重点,有个印象即可Redis为什么用单线程?多线程的开销通常情况下,在采用多线程后,如果没有良好的系统设计,其实是左图所展示的那样(注意纵坐标)。刚开始增加线程数时,系统吞吐率会增加,再进一步增加线程时,系统吞吐率就增长迟缓了,甚至还会出现下降的情况。上面两张图的标题手误被我标反了,源码还删了=_=关键瓶颈在于: 系统中通常会存在会被多线程同时访问的共享资源,为了保证共享资源的正确性,就需要有额外的机制保证线程安全性,例如加锁,这会带来额外的开销。比如拿最常用的List类型来举例吧,假设Redis采用多线程设计,有两个线程A和B分别对List做LPUSH和LPUSH操作,为了使得每次执行都是相同的结果,即【B线程取出A线程放入的数据】就需要让这两个过程串行执行。这就是多线程编程模式面临的共享资源的并发访问控制问题。并发访问控制一直是多线程开发中的一个难点问题:如果只是简单地采用一个互斥锁,就会出现即使增加了线程,大部分线程也在等待获取互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。同时加入并发访问控制后也会降低系统代码的可读性和可维护性,所以Redis干脆直接采用了单线程模式。Redis使用单线程为什么还这么快?之所以使用单线程是Redis设计者多方面衡量的结果。Redis的大部分操作在内存上完成采用了高效的数据结构,例如哈希表和跳表采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率既然Redis使用单线程进行IO,如果线程被阻塞了就无法进行多路复用了,所以不难想象,Redis肯定还针对网络和IO操作的潜在阻塞点进行了设计。网络与IO操作的潜在阻塞点在网络通信里,服务器为了处理一个Get请求,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从socket中读取请求(recv),解析客户端发送请求(parse),最后给客户端返回结果(send)。最基本的一种单线程实现是依次执行上面的操作。上面标红的accept和recv操作都是潜在的阻塞点:当Redis监听到有连接请求,但却一直不能成功建立起连接时,就会阻塞在accept()函数这里,其他客户端此时也无法和Redis建立连接当Redis通过recv()从一个客户端读取数据时,如果数据一直没有到达,也会一直阻塞基于多路复用的高性能IO模型为了解决IO中的阻塞问题,Redis采用了Linux的IO多路复用机制,该机制允许内核中,同时存在多个监听套接字和已连接套接字(select/epoll)。内核会一直监听这些套接字上的连接或数据请求。一旦有请求到达,就会交给Redis处理,这就实现了一个Redis线程处理多个IO流的效果。此时,Redis线程就不会阻塞在某一个特定的客户端请求处理上,所以它可以同时和多个客户端连接并处理请求。回调机制select/epoll一旦监测到FD上有请求到达时,就会触发相应的事件被放进一个队列里,Redis线程对该事件队列不断进行处理,所以就实现了基于事件的回调。例如,Redis会对Accept和Read事件注册accept和get回调函数。当Linux内核监听到有连接请求或读数据请求时,就会触发Accept事件和Read事件,此时,内核就会回调Redis相应的accept和get函数进行处理。Redis的性能瓶颈点经过上面的分析,虽然通过多路复用机制可以同时监听多个客户端的请求,但Redis仍然有一些性能瓶颈点,这也是我们平时编程需要极力避免的情况。1. 耗时操作任意一个请求在Redis中一旦耗时较久,都会影响整个server的性能。后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。这一点需要我们在设计业务场景时去规避;Redis的lazy-free机制也把释放内存的耗时操作放在了异步线程中去执行了。2. 高并发场景并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但还是只能单线程依次读取客户端的数据,无法利用到CPU多核。Redis在6.0可以利用CPU多核多线程读写客户端数据,但只是针对客户端的读写是并行的,每个命令的真正操作还是单线程。其他Redis相关的有趣问题借此机会也提几个和redis相关的有意思的问题。1. 为什么要用Redis,直接访问内存不好吗?这一条其实并没有很明确的界定,对于一些不经常变动的数据,可以直接放到内存里,不一定要放到Redis里,可以放到内存里。一致性问题:如果一个数据被修改了,数据在本地内存里的话,可能只有一台服务器上的数据被修改了。如果用Redis里面的话,我们访问Redis服务器,可以解决一致性问题。2. 数据太多内存放不下怎么办?比如我要缓存100G的数据,怎么办?这里也要打一个广告Tair是淘宝开源的分布式KV缓存系统,它从Redis继承了丰富的操作,理论上总数据量无限制,针对可用性、可扩展性、可靠性也进行了升级,感兴趣的小伙伴们可以了解一下~原文链接:https://blog.csdn.net/HNU_Csee_wjw/article/details/122567260
-
链接:https://bbs.huaweicloud.com/blogs/3359231 偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。在:创建一个线程并在线程中执行循环监听的场景下或单线程操作一个线程安全集合时同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。获取偏向锁的场景:在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:① : 先判断Mard Word的Thread ID是否有值没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)有,则表示当前资源被线程占用,需要判断该线程是不是自己该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID。一旦出现多个线程竞争时必须撤销偏向锁,所以:撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗不然得不偿失!JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。2 偏向锁的获取偏向锁的入口,synchronizer.cpp 文件的ObjectSynchronizer::fast_enter由BiasedLocking::revoke_and_rebias实现2.1 markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word2.2 判断mark是否为可偏向状态mark的偏向锁的锁标志位为 012.3 判断mark中JavaThread的状态若指向当前线程,则执行同步代码块若为空,则走4若指向其它线程,则走52.4 执行CAS原子指令设置mark中JavaThread为当前线程ID。若CAS成功,则执行同步代码块,否则走5。2.5 执行CAS失败说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。升级完成后被阻塞在安全点的线程继续执行同步代码块。BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) { assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint"); // We can revoke the biases of anonymously-biased objects // efficiently enough that we should not cause these revocations to // update the heuristics because doing so may cause unwanted bulk // revocations (which are expensive) to occur. // step1 markOop mark = obj->mark(); if (mark->is_biased_anonymously() && !attempt_rebias) { // We are probably trying to revoke the bias of this object due to // an identity hash code computation. Try to revoke the bias // without a safepoint. This is possible if we can successfully // compare-and-exchange an unbiased header into the mark word of // the object, meaning that no other thread has raced to acquire // the bias of the object. markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); markOop prototype_header = k->prototype_header(); if (!prototype_header->has_bias_pattern()) { // This object has a stale bias from before the bulk revocation // for this data type occurred. It's pointless to update the // heuristics at this point so simply update the header with a // CAS. If we fail this race, the object's bias has been revoked // by another thread so we simply return and let the caller deal // with it. markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked"); return BIAS_REVOKED; } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { // The epoch of this biasing has expired indicating that the // object is effectively unbiased. Depending on whether we need // to rebias or revoke the bias of this object we can do it // efficiently enough with a CAS that we shouldn't update the // heuristics. This is normally done in the assembly code but we // can reach this point due to various points in the runtime // needing to revoke biases. if (attempt_rebias) { assert(THREAD->is_Java_thread(), ""); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop prototype_header = k->prototype_header(); if (mark->biased_locker() == THREAD && prototype_header->bias_epoch() == mark->bias_epoch()) { // A thread is trying to revoke the bias of an object biased // toward it, again likely due to an identity hash code // computation. We can again avoid a safepoint in this case // since we are only going to walk our own stack. There are no // races with revocations occurring in other threads because we // reach no safepoints in the revocation path. // Also check the epoch because even if threads match, another thread // can come in with a CAS to steal the bias of an object that has a // stale epoch. ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr("Revoking bias by walking my own stack:"); } EventBiasedLockSelfRevocation event; BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); assert(cond == BIAS_REVOKED, "why not?"); if (event.should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { EventBiasedLockRevocation event; VM_RevokeBias revoke(&obj, (JavaThread*) THREAD); VMThread::execute(&revoke); if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } assert((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); EventBiasedLockClassRevocation event; VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias); VMThread::execute(&bulk_revoke); if (event.should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit(); } return bulk_revoke.status_code(); }3 偏向锁的撤销只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:void BiasedLocking::revoke_at_safepoint(Handle h_obj) { assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint"); oop obj = h_obj(); HeuristicsResult heuristics = update_heuristics(obj, false); if (heuristics == HR_SINGLE_REVOKE) { revoke_bias(obj, false, false, NULL, NULL); } else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) { bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL); } clean_up_cached_monitor_info(); }偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:-XX:BiasedLockingStartupDelay=0若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:XX:-UseBiasedLocking=false(默认打开)偏向锁的释放遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。总结当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率偏向锁的重入计数依靠线程栈里Lock Record个数偏向锁撤销失败,最终会升级为轻量级锁偏向锁退出时并没有修改Mark Word,也就是没有释放锁偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁】
上滑加载中
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中
热门标签