-
Redis是一种键值数据库,有着时延低、性能好、数据结构丰富的特点,常用作缓存、排行榜、计数器、 消息队列等,是电商秒杀、聊天系统等业务场景中的“熟客”。作为一个“缓存中间商”,Redis的性能问题至关重要,一旦发生操作延迟问题,很容易引起连锁反应。所以本文聚焦Redis的性能分析,从Redis的基本概念出发,了解Redis是什么,它的运行机制,思考Redis可以通过哪些机制来提高性能,当性能瓶颈发生的时候,我们又能做出哪些优化策略,最终确保业务系统的稳定运行。读懂Redis:缓存神器原来是这样工作的一个网站总有大量的数据是用户共享的,如果每个用户都去数据库查询,效率就太低了。所以有了新的解决方案:将用户共享数据缓存到服务器的内存中。举个例子,应用程序们从MySQL查询到的数据,会到Redis这里登记,后面再需要用的时候,就先查找Redis的缓存,无需返回到MySQL查找。一套流程下来,为MySQL减轻了不小的负担,网络服务的性能显著提升。Redis堪称数据库届的万金油,哪里需要往哪里搬,这也得益于它有着丰富的数据结构,以及强大的读写性能。以数据结构为例,Redis和其他结构化存储的重要区别便是,它不仅支持字符串,还支持不同类型的抽象数据结构,如列表、映射、集、排序集、HyperLogLogs、位图、流和空间索引等。那么Redis是如何做到如此“万能”的,它支持的这些数据结构又是如何从底层实现呢?《三次给你聊清楚Redis》之Redis是个啥 就从非关系型数据库谈起,详细聊了聊这个问题,就像最简单的字符串,Redis并未沿袭传统c语言的惯例,而是单独构建了一种简单的动态字符串抽象类型,并充分利用SDS实现。当然,如果你想进一步了解Redis系统的设计理念,比如它通过什么机制将数据缓存到内存中,开发大系统必备技术之Redis技术学习与研究或许会给你一些启发,作者谈到了Redis的历史、流行度、设计思想,并通过支持Redis的Java客户端Jedis ,用详尽的代码案例一步步演示了它支持的数据类型使用方法,它的事务特性、集群等等,更为具象地了解Redis的特点。当我们对Redis的基本原理了然于胸后,再针对业务场景进行优化时,也能更合理地使用各种Redis命令。Redis性能:祸福相依的内部运行机制Redis的最大特点是使用内存来存储数据,当内存超过物理内存的限制后,内存数据会和磁盘产生频繁的交换,最终导致Redis性能急剧下降。所以在生产环境中我们通过配置参数maxmemoey来限制使用的内存大小。 在有趣的Redis:缓存被我写满了,该怎么办? 中,作者详细解释了2个常见的缓存淘汰算法LRU算法和LFU算法,如何删除那些没用的数据。另一方面,Redis为了把内存中的数据持久到磁盘上,也提供了完善的持久化机制,主要包括2种:RDB:产生一个数据快照文件AOF:实时追加命令的日志文件但是如果配置不合理,持久化会占用过多内存从而影响性能。举个例子,如果AOF的刷盘时机设置为每次写入都刷盘,由于每次写命令都需要写入文件并刷到磁盘中才会返回,当写入量很大时,会增加磁盘IO的负担,大大降低Redis的写入性能。Redis 持久化是如何做的?一文聊聊 RDB和AOF对比分析 谈到了这两种持久化机制对Redis性能的影响,建议大家针对不同的业务场景选择合适的持久化方式。在讨论Redis性能问题的时候,不得不提的一点是它的单线程结构,这里的单线程指的是执行命令 ,比如一条命令从客户端到达服务端不会立刻被执行,而是会进入一个队列中等待,每次只会有一条指令被选中执行。【Redis破障之路】:Redis单线程架构 详细分析了单线程模型的Redis为什么性能如此之高,能达到每秒万级别的处理能力,简单透露两点:纯内存访问、I/O多路复用技术,具体可以阅读文章。而Redis的单线程架构,也意味着网络问题会对它的性能产生一定的影响。另外,当业务规模扩大,单个Redis服务无法承载的时候,我们常常会用分布式架构来提高Redis的性能,Redis主从复制以及哨兵的原理解读 和 Redis Sentinel 源码:Redis的高可用模型分析 都讨论了主从模式下的关键功能:哨兵,通过对其源码的理解,详细说明了哨兵的代码实现方式,并学会使用哨兵功能解决主节点的写能力、存储能力限制等等。除此之外,诸如数据结构的复杂度、网络带宽、操作系统以及硬件本身都会对Redis的性能产生影响,它的性能问题几乎涵盖了 CPU、内存、网络、磁盘的方方面面,再此不一一赘述。综上,我们分析了影响Redis性能的一些关键内部机制,比如它的缓存淘汰算法;它的持久化会占用过多内存从而影响性能;它的单线程架构等。通过了解Redis的这些内部实现原理,也能进一步帮助大家排查它的性能问题。Redis调优:宕机怎么办?收下这几颗灵丹妙药下面,我们将给出一些应对Redis性能问题的解决方案。以常见的缓存问题为例,通常情况下,Redis缓存层由于某种原因宕机后,所有的请求会涌向存储层,短时间内的高并发请求可能会导致存储层挂机,称之为“Redis雪崩”。Redis缓存异常应对方案分析 有针对性的总结了Redis发生缓存穿透、雪崩、击穿情况时,能够有效应对的解决方案,比如不要给访问频繁的热点数据设置过期时间,从而解决Redis实例没有起到缓存层作用的问题。大key也是影响Redis性能的关键因素,如果一个 key 写入的 value 非常大,那么 Redis 在分配内存时就会比较耗时。同样的,当删除这个 key 时,释放内存也会比较耗时,这种类型的 key 我们一般称之为 大key。 在 分布式缓存数据库Redis大KEY问题定位及优化建议 中,作者就针对数据库报错OOM来一步步分析大key的问题,先是查看Redis集群内存监控指标,确认内存异常分片,然后通过在线&离线工具分析,结果显示大key导致数据大小分布不均。对此作者给出了两个方案:短期是删除查询到的key,长期是对大key进行拆分。另一个经常被诟病性能问题的是fork, fork是开源Redis的一个重要依赖,当 Redis 开启了后台 RDB 和 AOF rewrite 后,在执行时,它们都需要主进程创建出一个子进程进行数据的持久化,fork就是创建子进程的系统调用函数。在华为云GaussDB(for Redis)服务团队支撑某客户业务上云的过程中 ,就发现了由fork引发的时延抖动问题,文章一场由fork引发的超时,让我们重新探讨了Redis的抖动问题 还原了当时的场景,探究了fork对性能的影响,包括业务抖动、内存率利用率降低和实例容量受限。比如,在电商大促、热点事件等业务高峰时发生上述fork,会导致Redis阻塞,进而对业务造成雪崩的影响。团队通过修改日志、系统性排查整改代码中的 fork调用,最后在新版本GaussDB(for Redis)中解决了该问题,并清零了内部的fork使用,与原生Redis相比,彻底解决了fork的性能隐患。其实,考虑到业务场景越来越复杂,原生Redis出现性能瓶颈难以避免。这时候,最简单粗暴的解决方法就是使用商业版本的Redis,一劳永逸解决可能存在的性能问题。在 GaussDB(for Redis)与原生Redis集群的性能对比 中,就比较了华为云自研Redis和原生Redis集群在X86架构下的性能测试报告,结果表明GaussDB(for Redis)在性能、抗写和存储成本上的优势明显。从相识到相惜:Redis与计算存储分离四部曲 进一步从技术角度拆解分析了GaussDB(for Redis)如何在存算分离的架构下,实现强一致、秒扩容、超可用、低成本。以强一致为例,Redis遇到流量压力进行主从切换时很容易发生数据不同步问题,GaussDB ( for Redis)就在存储层(DFV层)去进行强一致的数据同步,而非计算层,这样就避免了任何中间态下的数据的不一致,再也不用担心宕机导致数据丢失。更多的技术细节揭秘,也可以阅读这组专题高斯Redis揭秘系列文章,更全面的认识GaussDB ( for Redis)。Redis的性能问题,涉及到的技术细节很多,本专题只是列出了一些较为典型的问题,希望读者能够通过上述提及的技术文章,对它有更深入的认识,学会从底层运行机制去思考Redis的性能调优。转自,【技术补给站】第10期:分析内部运行机制,教你解决Redis性能问题-云社区-华为云 (huaweicloud.com)
-
本文将对GausDB(DWS)中资源管理应用的cgroup技术做简单的概述。1、GausDB(DWS)中涉及到的子系统1.1 Memorycgroup.event_control #用于eventfd的接口memory.usage_in_bytes #显示当前已用的内存memory.limit_in_bytes #设置/显示当前限制的内存额度memory.failcnt #显示内存使用量达到限制值的次数memory.max_usage_in_bytes #历史内存最大使用量memory.soft_limit_in_bytes #设置/显示当前限制的内存软额度memory.stat #显示当前cgroup的内存使用情况memory.use_hierarchy #设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面memory.force_empty #触发系统立即尽可能的回收当前cgroup中可以回收的内存memory.pressure_level #设置内存压力的通知事件,配合cgroup.event_control一起使用memory.swappiness #设置和显示当前的swappinessmemory.move_charge_at_immigrate #设置当进程移动到其他cgroup中时,它所占用的内存是否也随着移动过去memory.oom_control #设置/显示oom controls相关的配置memory.numa_stat #显示numa相关的内存一旦设置了内存限制,将立即生效,并且当物理内存使用量达到limit的时候,memory.failcnt的内容会加1,但这时进程不一定就会被kill掉,内核会尽量将物理内存中的数据移到swap空间上去(swappiness参数可以选择是否使用swap),如果实在是没办法移动了(设置的limit过小,或者swap空间不足),默认情况下,就会kill掉cgroup里面继续申请内存的进程。当物理内存达到上限后,系统的默认行为是kill掉cgroup中继续申请内存的进程,那么怎么控制这样的行为呢?答案是配置memory.oom_control如果写0到这个文件,将启动OOM-killer,当内核无法给进程分配足够的内存时,将会直接kill掉该进程;如果写1到这个文件,表示不启动OOM-killer,当内核无法给进程分配足够的内存时,将会暂停该进程直到有空余的内存之后再继续运行;同时,memory.oom_control还包含一个只读的under_oom字段,用来表示当前是否已经进入oom状态,也即是否有进程被暂停了。1.2. CPUCPU是用来设置CPU配额的子系统,所谓配额是指CPU的相对使用比例。配置CPU子系统的方法有两种:一种是通过设置cpu.cfs_quota_us 和 cpu.cfs_period_us二者中的数值,来配置该cgroup中任务所占据的比例。举个例子,cfs_period_us的值为100,表明一个cpu时间周期内100ms, cfs_quota_us 的值为200, 表明在一个cpu周期上,该cgroup中的任务使用了200ms的时间,这表示该cgroup中的任务占用了两个cpu核心。另外一个配置方法是修改cpu.shares中的数值,这个数值表明系统中若干个cgroup所占用cpu的比例值。例如系统中有两个cgroup,分别为A与B,A的数值为1024(默认值),B的数值为2048,那么cgroup B所能占用的CPU时间为系统全部cpu时间的比例为:2048/(1024+2048) = 2/3 = 66.6%cpu.shares的配置方法有一个好处,就是如果系统没有在cpu资源占满的情况下,是不会触发这个机制的。也就是说,即便是cgroup A占有的cgroup比例仅为33.3%, 在cgroup B中的任务不繁忙的时候,cgroup A中的任务仍然可以使用超过33.3%的资源。与配置cfs调度中的时间片占用比例相比,使用shares参数可以使cpu的利用率得到提高。从上面我们能够看出来,cgroup提供的cpu控制组是比较纠结的,一个通过配置占据cpu时间片的比例,来将cpu占用比例的上限写死了,另一个cpu.shares参数虽然是动态的,但是如果cgroup的数量增加了,这个相对比例值也会发生变化。因此,在cpu子系统的配置还是很有讲究的。1.3. CPUACCT这个子系统没有什么实际限制作用,只是起到了对cgroup中任务占用cpu资源的统计,systemd将该子系统与cpu关联在一起挂载,可以通过查看cpuacct.usage来获取cgroup中任务所占用的总的cpu使用情况cpuacct.usage_percpu可以细粒度地看到每一个核心的使用情况。mppdb没有实际使用到cpuacct中的功能,在这里不做具体介绍。1.4. CPUSET该参数用于配置cgroup所使用cpu资源的限额,所谓限额,就是指使用的固定cpu核心数。可以通过配置cpuset.cpus参数来指定任务所使用的cpu核心数。默认的cgroup中cpu核心数是全部使用的,例如:0-119, 表明了系统共有120个cpu核心,全部都使用。需要注意的是,子cgroup中的cpu核心必须是父cgroup中cpu核心的子集,如果需要修改cpuset.cpus参数,需要从根节点向父节点修改,否则提示无法修改。2、GaussDB(DWS)中cgroup的应用2.1控制作业优先级我们可以通过资源池来与cgroup关联,可以通过资源池来指定任务的运行优先级,可以指定为rush, normal等不同的类别,这是通过cgroup中的cpu子系统来实现的。GaussDB(DWS)在安装的时候,会为系统默认创建cgroup, 命名规则为 Gaussdb: 用户名, 这个cgroup中会包含许多层级,例如Class,Backend等子cgroup. 2.2保障Gaussdb(DWS)线程优先级GaussDB(DWS)的后台线程(backend threads) 纳入cgroup保护,主要是vacuum线程等。转载来自:https://bbs.huaweicloud.com/blogs/269406
-
【摘要】 GaussDB(DWS)的实例在运行过程中难免发生故障,引起实例错误或无法启动,此时就需要进行备机重建工作。备机重建功能主要目的是修复实例单点故障,此外,还可用于集群安装的初始化、集群扩容的元数据同步、节点故障后的温备替换等等场景。本文将介绍备机重建各种方法的实现机制,并结合应用场景分析,以及对新增参数的使用建议,以期获得最佳应用效果。1 需求介绍 GaussDB(DWS)的实例在运行过程中难免发生故障,引起实例错误或无法启动,此时就需要进行备机重建工作。备机重建功能主要目的是修复实例单点故障,此外,还可用于集群安装的初始化、集群扩容的元数据同步、节点故障后的温备替换等等场景。本文将介绍备机重建各种方法的实现机制,并结合应用场景分析,以及对新增参数的使用建议,以期获得最佳应用效果。2 设计方案2.1 功能分类 备机重建按照实现方式不同分为:全量重建和增量重建。 备机重建需要在待修复主机上运行gs_ctl build工具。在备机重建过程中,均需要和主DN建立连接进行数据的交互访问。在gs_ctl工具的命令行参数中通过–b=mode参数,可以指定重建DN备机的模式。目前mode支持的取值包括以下四种: full:全量重建,通过获取主备之间全量镜像差异的方式重新同步DN主机的数据目录。 fullcleanup:全量重建,通过全量镜像的方式重新同步DN主机的数据目录。与full模式区别为:同步前需要清理DN备机的数据目录,保留配置文件。主机将自己的数据目录除了配置文件外,全部发给备机。 incremental:增量重建,通过解析WAL日志获取主备DN差异的数据进行增量修复备DN。 auto(不指定-b):先增量重建,增量重建失败后进行全量重建。 实际生产环境中,具体使用哪种方式,就需要看需求与应用场景所定。2.2 应用场景 备机重建按照功能场景不同分为:DN Build DN、CN build CN、CN Build DN。各Build的应用场景的特点如下:编号场景使用方式特点分析可选模式1安装DN build DN 初始化数据总量小。 无性能和备份需求;有约束,无GTM支撑,直接拉起DN进行Build。fullfullcleanup 2扩容CN build CNCN build DNDN build DN 主机上元数据小文件总量多。备机从无到有,无历史数据。无备份需求;有小文件性能需求;有约束,CN支持一对多的Build,扩容过程中锁集群。fullfullcleanup 3温备替换DN build DN 数据总量比较多。备机从无到有。无备份需求有性能需求有约束,温备过程中锁集群fullfullcleanup 4修复DN build DN 通常,备机上有相当多的数据文件,而且在备机损坏较短时间内,主备之间存在相当多的相同文件。有备份需求有性能需求incrementalfullfullcleanup 表1 Build的应用场景 选择何种修复方式最优,需要理解该模式的工作原理,并根据应用场景合理地设置相关参数。3 实现过程3.1 fullcleanup mode:基于推送方式 fullcleanup mode是一种推送模式,在该模式下,由主机来控制数据流程(我给啥你接啥),在不关心备机损坏程度和范围的情况下,需要主机将自己的数据目录配置文件外的全部数据传送给备机,重建后启动备机。 其主要工作过程如图1所示:图1 fullcleanup build工作过程 fullcleanup mode build的特点很明显,备机将得到彻底重建。但缺点也很明显:主机需要Copy实例上全部的数据和XLOG日志文件,占用较高的网络传输带宽,对运行中的业务有一定影响。备机没有对修复前数据进行原子化管理,过程中一旦失败将无法恢复到原备机。如果全量Build过程中由于偶发网络等原因故障失败,将前功尽弃,再次Build数据需要从头再来。 所以该方式是最保守的最后选项,是在其它重建方式均无效的时候的一种选择。3.2 full mode:基于文件校验获取差异的拉取方式 full mode是一种拉取模式,在该模式下,由备机来控制数据流程(我需要啥拿啥),仅需要补齐主备之间的差异数据。但前提是备机需要知晓自己与主机差异。full mode是直接从文件比较进行切入,主、备机可以同时多线程(并行、并发,提升性能)在各自本地上对数据目录文件进行遍历,获知文件有无、大小、文件校验计算等信息;通过这些信息,不断对计算结果File Map List进行合并筛选,获得最小的差异文件集合,减少数据/文件的拷贝数量。备机既可以仅备份备机的差异文件作为备份集(满足可靠性),又可以仅拉取主机的差异文件来更新(提升性能);备机可以采用多线程(并发,提升性能)同时从主机拉取文件。 其主要工作过程如图2所示:图2 full build工作过程 full mode build的特点是充分利用备机已有的文件,减少数据同步的数量,可以方便地进行备份和恢复、和并行控制。但需要花费一定时间的本地IO和计算为代价。与fullcleanup mode相比较在无资源瓶颈条件下通常速度更快(可获得数倍的提升),可靠性上更安全,是全量build的首选。3.3 incremental mode:基于Xlog分析获取差异的拉取方式 incremental mode是另外一种拉取方式。适用于主备双主等因日志造成的不一致场景。增量重建是一种以主DN文件和备DN的WAL日志为基准,按照多退少补的原则对备DN文件和文件块进行修复。与全量重建相比较,拷贝的粒度、数据量和WAL日志量都更少,代价更小。 其主要工作过程如图3所示:图3 incremental build工作过程 incremental mode build只适用于主备双主等因日志造成的不一致场景。备机数据文件损坏、数据目录丢失等故障通过增量重建的方式无法修复,此时可通过全量重建的方式重新修复备机。4 思考总结 随着备机重建功能的性能提升和可靠性的增强,build新增了部分参数,在使用时候值得了解和注意。4.1 -T THREAD-NUMBER 适用于拉取方式的full和incremental模式。其功能是在备机侧指定与主机连接个数,用于多线程并发进行计算和与文件拉取。 通常情况下默认值为4已可获得较好的性能,在资源允许的情况下,建议使用更高的线程数。但需注意的是增大线程数目可以提升Build的性能,缩短其重建时间,但相应的也增加了网络连接、CPU、和网络IO的消耗,在使用时候需要考虑资源状况,合适设置。4.2 -u 适用于支持原子化功能的full和incremental模式。其功能是Build过程中将不进行原子化恢复和备份,适合于空间不足或无需备份的需要场景中。 Incremental build默认是原子化,过程中严格进行原子化恢复和备份,过程中出错可恢复,排除错误后,可再次调用重入。由于Full Build的涉及的数据文件量大,默认无原子化,但会尝试进行原子化恢复,忽略恢复结果成败,仅在备机拉取主机上的文件占其总量20%内进行备份。在使用时最好明确指出需要或者不需要原子化功能。4.3 -B --backupdir=DIR 适用于支持原子化功能的full和incremental模式。其功能是Build过程中将从指定的路径下进行备份集的恢复和备份。适合于强高可靠需求场景,在Build成功之前可保持备份的原子化,避免原始备份丢失。 需要注意的是,用户指定备份集路径的磁盘剩余空间应大于DN实例数据目录大小。且备份路径下保持空白或者是本节点的数据集,无关数据将会造成备份集失效,重新生成的备份集会让原始数据副本有丢失的风险。除pg_rewind_bak路径外,用户指定备份集路径的应与备机实例工作路径隔离。 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技~转载来自:https://bbs.huaweicloud.com/blogs/251510
-
新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。线程的优先级每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。创建一个线程Java 提供了三种创建线程的方法:通过实现 Runnable 接口;通过继承 Thread 类本身;通过 Callable 和 Future 创建线程。通过实现 Runnable 接口来创建线程创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:public void run()你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。Thread 定义了几个构造方法,下面的这个是我们经常使用的:Thread(Runnable threadOb,String threadName);这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。新线程创建之后,你调用它的 start() 方法它才会运行。void start();下面是一个创建线程并开始让它执行的实例:实例class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); } }编译以上程序运行结果如下:Creating Thread-1Starting Thread-1Creating Thread-2Starting Thread-2Running Thread-1Thread: Thread-1, 4Running Thread-2Thread: Thread-2, 4Thread: Thread-1, 3Thread: Thread-2, 3Thread: Thread-1, 2Thread: Thread-2, 2Thread: Thread-1, 1Thread: Thread-2, 1Thread Thread-1 exiting.Thread Thread-2 exiting.通过继承Thread来创建线程创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。实例class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo( "Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2"); T2.start(); } }编译以上程序运行结果如下:Creating Thread-1Starting Thread-1Creating Thread-2Starting Thread-2Running Thread-1Thread: Thread-1, 4Running Thread-2Thread: Thread-2, 4Thread: Thread-1, 3Thread: Thread-2, 3Thread: Thread-1, 2Thread: Thread-2, 2Thread: Thread-1, 1Thread: Thread-2, 1Thread Thread-1 exiting.Thread Thread-2 exiting.
-
科技推动了人类历史上多次革命。第一次革命,伴随着蒸汽机的轰鸣,机器取代了部分人力。第二次革命,电力的出现,人类进入电气时代。而着眼当下,数字化的浪潮袭来,人们的生活的多方便都在数字化中。大数据、人工智能、区块链、云计算等前沿技术,赋予数字化革命更多的想象空间。当我们的金钱、信息、娱乐都建立在数字化基础上后,我们仍然在畅想着将更多的东西数字化。从某个角度而言,反应信息化社会的发达程度,来自于社会的数字化程度以及物质生活的数字化程度。我们从传统的实体去探讨数字。在一家工厂,我们具有机器和工人。在工业化的早期,机器是生产工具,工人是使用生产工具的人。而在工业自动化越来越高的今天,工人不再是驱动机器的主要因素,更多的是辅助角色。完成机器无法自动化完成的工作;衔接机器与机器的对接,整合机器与机器之间两个独立的流程;根据工厂的自动化程度,工人的数量是个变量。工人所需要的多少,取决于 流水线机器的自动化程度,机器与机器之间衔接的完整度等。以较高的时间维度来看,工人在工厂的数量是不断减少的。现在甚至在出现无人工厂。现在我们回过头来讨论信息化中的员工角色。例如在财务工作中,软件/信息系统、财务人员。在信息化程度的还不高的早期阶段,我们所具有的往往只是可以做为辅助的软件系统,对财务人员的需求是大量的。而目前,信息化程度以及较高,财务人员目前所做的工作内容分为以下几个方面:1,将物理世界中的信息转化成信息系统中的内容2,衔接软件系统与软件系统3,完成软件系统尚未实现的工作那么随着信息化程序的不断攀高,我们是否可以在信息化的工作中,实现类似无人工厂的无人化?什么是数字员工?在信息化工作中,我们使用一种技术,模拟员工进行信息化工作,我们称之为“数字员工”。在财务工作中,我们是否能使用一种技术,代替掉目前财务人员目前的上述3个方面工作,让财务人员从中解脱出来,将更多的精力投入到业务分析和经营决策中呢?数字员工的技术支点:RPA什么是RPA?RPA是一种概念, RPA是Robotic Process Automation(机器人流程自动化)的缩写,机器人特指软件机器人。该术语由英国RPA供应商Blue Prism首席传道者(Chief Evangelist) Pat Geary在2012年提出,其市场和概念不断迭代更新。RPA综合定义为:一种以软件机器人及人工智能(AI)为基础的业务流程自动化工具及平台,以软件形式安装于个人计算机、大型服务器或云端,可根据已设定规则自动执行流程任务,代替或辅助人类完成各种重复性操作。由于RPA可以减少重复性劳动,降低企业人力负担、提高效率,被称为企业“数字员工”。从行业来看,RPA主要应用在金 融、制造等领域;从场景来看,RPA主要在财务、供应链等需要重复性工作的业务流程中发挥价值,本报告将重点研究RPA 落地实践及对企业数字化转型的赋能。传统的RPA技术和相关软件,已出现多年。即使在RPA概念出现前。国内早年出现的按键精灵都属于RPA概念下的软件,按键精灵立志于解决游戏的自动化,以及少量办公场景。在国外,早在1994年,微软就发布了Excel 5.0中的Macroinstruction(宏指令)功能,早期这类编程工具如批 处理脚本和触发器等皆为RPA的雏形,微软孕育了大部分底层自动化框架和技术。随着数据库和编程技术的成熟,专业的RPA工具 从21世纪初开始建立,并诞生UiPath、Blue Prism、Automation Anywhere等独角兽企业。
-
代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void set(String name, int age){this.name=name;try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.age=age;}public synchronized void get(){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable {private Info info = null;Producer(Info info) {this.info = info;}public void run() {boolean flag = false;for (int i = 0; i < 25; ++i) {if (flag) {this.info.set("Rollen", 20);flag = false;} else {this.info.set("ChunGe", 100);flag = true;}}}}/*** 消费者类* */class Consumer implements Runnable {private Info info = null;public Consumer(Info info) {this.info = info;}public void run() {for (int i = 0; i < 25; ++i) {try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */class hello {public static void main(String[] args) {Info info = new Info();Producer pro = new Producer(info);Consumer con = new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100从运行结果来看,错乱的问题解决了,现在是Rollen 对应20,ChunGe对于100,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、,我们可以使用其中的等待和唤醒操作。要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void set(String name, int age){if(!flag){try{super.wait();}catch (Exception e) {e.printStackTrace();}}this.name=name;try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.age=age;flag=false;super.notify();}public synchronized void get(){if(flag){try{super.wait();}catch (Exception e) {e.printStackTrace();}}try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());flag=true;super.notify();}private String name = "Rollen";private int age = 20;private boolean flag=false;}/*** 生产者* */class Producer implements Runnable {private Info info = null;Producer(Info info) {this.info = info;}public void run() {boolean flag = false;for (int i = 0; i < 25; ++i) {if (flag) {this.info.set("Rollen", 20);flag = false;} else {this.info.set("ChunGe", 100);flag = true;}}}}/*** 消费者类* */class Consumer implements Runnable {private Info info = null;public Consumer(Info info) {this.info = info;}public void run() {for (int i = 0; i < 25; ++i) {try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */class hello {public static void main(String[] args) {Info info = new Info();Producer pro = new Producer(info);Consumer con = new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【程序运行结果】:Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20
-
同步和死锁:【问题引出】:比如说对于买票系统,有下面的代码:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:543210-1这里出现了-1,显然这个是错的。,应该票数不能为负值。如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,其他的线程必须等到这个线程结束之后才能继续执行。【使用线程同步解决问题】采用同步的话,可以使用同步代码块和同步方法两种来完成。【同步代码块】:语法格式:synchronized(同步对象){//需要同步的代码}但是一般都把当前对象this作为同步对象。比如对于上面的买票的问题,如下:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){synchronized (this) {if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:(每一秒输出一个结果)54321【同步方法】也可以采用同步方法。语法格式为synchronized 方法返回类型 方法名(参数列表){// 其他代码}现在,我们采用同步方法解决上面的问题。复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for (int i = 0; i < 10; ++i) {sale();}}public synchronized void sale() {if (count > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count--);}}public static void main(String[] args) {hello he = new hello();Thread h1 = new Thread(he);Thread h2 = new Thread(he);Thread h3 = new Thread(he);h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】(每秒输出一个)54321提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。此处列举经典的生产者和消费者问题。【生产者和消费者问题】先看一段有问题的代码。复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable{private Info info=null;Producer(Info info){this.info=info;}public void run(){boolean flag=false;for(int i=0;i<25;++i){if(flag){this.info.setName("Rollen");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(20);flag=false;}else{this.info.setName("chunGe");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(100);flag=true;}}}}/*** 消费者类* */class Consumer implements Runnable{private Info info=null;public Consumer(Info info){this.info=info;}public void run(){for(int i=0;i<25;++i){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.info.getName()+"<---->"+this.info.getAge());}}}/*** 测试类* */class hello{public static void main(String[] args) {Info info=new Info();Producer pro=new Producer(info);Consumer con=new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<---->100chunGe<---->20chunGe<---->100Rollen<---->100chunGe<---->20Rollen<---->100Rollen<---->100Rollen<---->100chunGe<---->20chunGe<---->20chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20大家可以从结果中看到,名字和年龄并没有对于。那么如何解决呢?<!--[if !supportLists]-->1) <!--[endif]-->加入同步<!--[if !supportLists]-->2) <!--[endif]-->加入等待和唤醒先来看看加入同步会是如何。
-
2关于选择继承Thread还是实现Runnable接口?其实Thread也是实现Runnable接口的:复制代码代码如下:class Thread implements Runnable {//…public void run() {if (target != null) {target.run();}}}其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:http://www.cnblogs.com/rollenholt/archive/2011/08/18/2144847.htmlThread和Runnable的区别:如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello extends Thread {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello h1 = new hello();hello h2 = new hello();hello h3 = new hello();h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1大家可以想象,如果这个是一个买票系统的话,如果count表示的是车票的数量的话,说明并没有实现资源的共享。我们换为Runnable接口:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello implements Runnable {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello he=new hello();new Thread(he).start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1总结一下吧:实现Runnable接口比继承Thread类所具有的优势:1):适合多个相同的程序代码的线程去处理同一个资源2):可以避免java中的单继承的限制3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。所以,本人建议大家劲量实现接口。复制代码代码如下:/*** @author Rollen-Holt* 取得线程的名称* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();new Thread(he,"A").start();new Thread(he,"B").start();new Thread(he).start();}}【运行结果】:AAABThread-0Thread-0Thread-0说明如果我们没有指定名字的话,系统自动提供名字。提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。判断线程是否启动复制代码代码如下:/*** @author Rollen-Holt 判断线程是否启动* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he);System.out.println("线程启动之前---》" + demo.isAlive());demo.start();System.out.println("线程启动之后---》" + demo.isAlive());}} 【运行结果】线程启动之前---》false线程启动之后---》trueThread-0Thread-0Thread-0主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。线程的强制执行:复制代码代码如下:/*** @author Rollen-Holt 线程的强制执行* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he,"线程");demo.start();for(int i=0;i<50;++i){if(i>10){try{demo.join(); //强制执行demo}catch (Exception e) {e.printStackTrace();}}System.out.println("main 线程执行-->"+i);}}}【运行的结果】:main 线程执行-->0main 线程执行-->1main 线程执行-->2main 线程执行-->3main 线程执行-->4main 线程执行-->5main 线程执行-->6main 线程执行-->7main 线程执行-->8main 线程执行-->9main 线程执行-->10线程线程线程main 线程执行-->11main 线程执行-->12main 线程执行-->13...线程的休眠:复制代码代码如下:/*** @author Rollen-Holt 线程的休眠* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(2000);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + i);}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();}}【运行结果】:(结果每隔2s输出一个)线程0线程1线程2线程的中断:复制代码代码如下:/*** @author Rollen-Holt 线程的中断* */class hello implements Runnable {public void run() {System.out.println("执行run方法");try {Thread.sleep(10000);System.out.println("线程完成休眠");} catch (Exception e) {System.out.println("休眠被打断");return; //返回到程序的调用处}System.out.println("线程正常终止");}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();try{Thread.sleep(2000);}catch (Exception e) {e.printStackTrace();}demo.interrupt(); //2s后中断线程}}【运行结果】:执行run方法休眠被打断在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。复制代码代码如下:/*** @author Rollen-Holt 后台线程* */class hello implements Runnable {public void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在运行");}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.setDaemon(true);demo.start();}}虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。线程的优先级:复制代码代码如下:/*** @author Rollen-Holt 线程的优先级* */class hello implements Runnable {public void run() {for(int i=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);}}public static void main(String[] args) {Thread h1=new Thread(new hello(),"A");Thread h2=new Thread(new hello(),"B");Thread h3=new Thread(new hello(),"C");h1.setPriority(8);h2.setPriority(2);h3.setPriority(6);h1.start();h2.start();h3.start();}}【运行结果】:A运行0A运行1A运行2A运行3A运行4B运行0C运行0C运行1C运行2C运行3C运行4B运行1B运行2B运行3B运行4。但是请读者不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源、另外,主线程的优先级是5.线程的礼让。在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。复制代码代码如下:/*** @author Rollen-Holt 线程的优先级* */class hello implements Runnable {public void run() {for(int i=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);if(i==3){System.out.println("线程的礼让");Thread.currentThread().yield();}}}public static void main(String[] args) {Thread h1=new Thread(new hello(),"A");Thread h2=new Thread(new hello(),"B");h1.start();h2.start();}}A运行0A运行1A运行2A运行3线程的礼让A运行4B运行0B运行1B运行2B运行3线程的礼让B运行4
-
这一篇文章主要关于java多线程,主要还是以例子来驱动的。因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,呵呵、大家可以去参考一些那些书籍。我这个文章主要关于实际的一些问题。同时也算是我以后复习的资料吧,。呵呵大家多多指教。同时希望多结交一些技术上的朋友。谢谢。----------------------------------------------------------------------------------------------------------------------------------------------------java中的多线程在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。对于直接继承Thread的类来说,代码大致框架是:复制代码代码如下:class 类名 extends Thread{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}先看一个简单的例子:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,直接调用run方法* */class hello extends Thread {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.run();h2.run();}private String name;}【运行结果】:A运行 0A运行 1A运行 2A运行 3A运行 4B运行 0B运行 1B运行 2B运行 3B运行 4我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。当我们把上面的主函数修改为如下所示的时候:复制代码代码如下:public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.start();h2.start();}然后运行程序,输出的可能的结果如下:A运行 0B运行 0B运行 1B运行 2B运行 3B运行 4A运行 1A运行 2A运行 3A运行 4因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。那么:为什么我们不能直接调用run()方法呢?我的理解是:线程的运行需要本地操作系统的支持。如果你查看start的源代码的时候,会发现:复制代码代码如下:public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0 || this != me)throw new IllegalThreadStateException();group.add(this);start0();if (stopBeforeStart) {stop0(throwableFromStop);}}private native void start0();注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。通过实现Runnable接口:大致框架是:复制代码代码如下:class 类名 implements Runnable{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}来先看一个小例子吧:复制代码代码如下:/*** @author Rollen-Holt 实现Runnable接口* */class hello implements Runnable {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("线程A");Thread demo= new Thread(h1);hello h2=new hello("线程B");Thread demo1=new Thread(h2);demo.start();demo1.start();}private String name;}【可能的运行结果】:线程A运行 0线程B运行 0线程B运行 1线程B运行 2线程B运行 3线程B运行 4线程A运行 1线程A运行 2线程A运行 3线程A运行 4
-
前言学习了RT-Thread的内核组件与线程管理,我忽然发现,线程创建时,名字可以使用RT_NULL。创建无名线程,工作正常,只是,无法删除相关代码#include <rtthread.h> #define DBG_SECTION_NAME "noname" #define DBG_LEVEL DBG_LOG #include <rtdbg.h> /* led test */ extern void led_red_on(void); extern void led_red_off(void); extern void led_grn_on(void); extern void led_grn_off(void); extern void led_blu_on(void); extern void led_blu_off(void); /* no name thread1 */ static void no_name_task1(void* param) { LOG_D("no name thread1 start!\n"); while (1) { led_red_on(); rt_thread_mdelay(2000); led_red_off(); rt_thread_mdelay(2000); } } /* no name thread2 */ static void no_name_task2(void* param) { LOG_D("no name thread2 start!\n"); while (1) { led_blu_on(); rt_thread_mdelay(3000); led_blu_off(); rt_thread_mdelay(3000); } } /* no name thread3 */ static void no_name_task3(void* param) { LOG_D("no name thread3 start!\n"); while (1) { led_grn_on(); rt_thread_mdelay(4000); led_grn_off(); rt_thread_mdelay(4000); } } void no_name_init(void) { rt_thread_t tid; tid = rt_thread_create(RT_NULL, no_name_task1, RT_NULL, 2048, 28, 50); rt_thread_startup(tid); tid = rt_thread_create(RT_NULL, no_name_task2, RT_NULL, 2048, 28, 50); rt_thread_startup(tid); tid = rt_thread_create(RT_NULL, no_name_task3, RT_NULL, 2048, 28, 50); rt_thread_startup(tid); } void no_name_del(void) { rt_thread_t tid; tid = rt_thread_find(RT_NULL); if (tid != RT_NULL) { rt_thread_delete (tid); } else { LOG_D("Not find thread!"); } } MSH_CMD_EXPORT(no_name_init, no_name_init); MSH_CMD_EXPORT(no_name_del, no_name_del);msh >no_name_init msh >[D/noname] no name thread1 start! [D/noname] no name thread2 start! [D/noname] no name thread3 start! msh >list_thread thread pri status sp stack size max used left tick error -------- --- ------- ---------- ---------- ------ ------------ ----- " 28 suspend 0x0000007c 0x00000800 07% 0x0000002e 000 " 28 suspend 0x0000007c 0x00000800 07% 0x0000002e 000 " 28 suspend 0x0000007c 0x00000800 07% 0x0000002e 000 pms 28 suspend 0x0000009c 0x00000800 07% 0x0000002f 000 tshell 20 running 0x00000084 0x00001000 12% 0x00000005 000 serial 25 suspend 0x00000088 0x00000400 13% 0x0000000a 000 tidle0 31 ready 0x00000080 0x00000800 06% 0x00000016 000 timer 4 suspend 0x00000060 0x00000200 18% 0x00000009 000 msh > msh >no_name_del [D/noname] Not find thread! 解读之所能创建无名的线程,因为内核对象初始化时,并不判断name的长度之所无法删除没有名字的线程,是因为内核对象的查找,是用名字来查找的。总结需要继续研究RT-Thread内核代码、线程管理代码,正确使用RT-Thread加深基础理论知识的积累。————————————————
-
Lock和synchronized是最常见的锁,用于控制对共享资源的访问。Lock不是用来代替synchronized的,而是synchronized不满足要求时,提供更高级的功能。Lock接口常见实现类是ReentrantLock和ReadWriteLock。1、锁效率低 锁的释放情况少。synchronized监视器锁在方法或代码块正常退出和异常时释放锁。 试图获得锁时不能设置超时、不能中断一个正在试图获得锁的线程。2、不够灵活 相对于读写锁,加锁和释放的时机单一,每个锁只有单一条件(锁定某个对象),可能不够。3、无法知道是否成功获取到锁。 例如Lock的tryLock方法1 Lock接口分析public interface Lock { // 获取锁 void lock(); // 获取锁时可以被中断 void lockInterruptibly() throws InterruptedException; boolean tryLock(); // 超时就放弃 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 解锁 void unlock(); Condition newCondition(); } Lock不会像synchronized在异常时释放锁,因此需要在finally调用unlock,确保锁被释放。 lock()方法不能被中断,一旦死锁,会陷入永久等待。 tryLock()用来尝试获取锁,如果当前锁未被其他线程占用,则获取锁,并返回true;否则返回false。方法立即返回,即使拿不到锁也会等待。 tryLock代码规范,参考注释Lock lock = new ReentrantLock(); if(lock.tryLock()) { try { } finally { lock.unlock(); } } 线程等待Lock锁时是WAITING状态,不可能是BLOCKED.2 Lock的可见性保证 Lock的可见性与synchronized相同,可以参考happens-before原则。解锁操作对之后的加锁操作可见。 lock和unlock对齐内存交互操作中的lock和unlock操作。3 演示示例 本文案例代码位置:https://gitee.com/dtyytop/advanced-java————————————————
-
1、String String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。 String类的包含如下定义:[java] view plaincopy /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; 用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。 2、StringBuffer(JDK1.0) StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。 Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。 StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。3、StringBuilder(JDK5.0) StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。 java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。 其构造方法如下:构造方法描述StringBuilder()创建一个容量为16的StringBuilder对象(16个空元素)StringBuilder(CharSequence cs)创建一个包含cs的StringBuilder对象,末尾附加16个空元素StringBuilder(int initCapacity)创建一个容量为initCapacity的StringBuilder对象StringBuilder(String s)创建一个包含s的StringBuilder对象,末尾附加16个空元素 在大部分情况下,StringBuilder > StringBuffer。这主要是由于前者不需要考虑线程安全。4、三者区别 String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。 使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。 在某些特别情况下, String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:[java] view plaincopy String s1 = “This is only a” + “ simple” + “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); 生成 String s1对象的速度并不比 StringBuffer慢。其实在Java Compiler里,自动做了如下转换: Java Compiler直接把上述第一条语句编译为: [java] view plaincopy String s1 = “This is only a simple test”; 所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,Java Compiler就不会自动转换了,速度也就没那么快了,例如:[java] view plaincopy String s2 = “This is only a”; String s3 = “ simple”; String s4 = “ test”; String s1 = s2 + s3 + s4; 这时候,Java Compiler会规规矩矩的按照原来的方式去做,String的concatenation(即+)操作利用了StringBuilder(或StringBuffer)的append方法实现,此时,对于上述情况,若s2,s3,s4采用String定义,拼接时需要额外创建一个StringBuffer(或StringBuilder),之后将StringBuffer转换为String;若采用StringBuffer(或StringBuilder),则不需额外创建StringBuffer。5、使用策略(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。(2)不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。例如:[java] view plaincopy String result = ""; for (String s : hugeArray) { result = result + s; } // 使用StringBuilder StringBuilder sb = new StringBuilder(); for (String s : hugeArray) { sb.append(s); } String result = sb.toString(); 当出现上面的情况时,显然我们要采用第二种方法,因为第一种方法,每次循环都会创建一个String result用于保存结果,除此之外二者基本相同(对于jdk1.5及之后版本)。 (3)为了获得更好的性能,在构造 StirngBuffer 或 StirngBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。(4)StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。(5)相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。————————————————
-
1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。2.JDK和JRE的区别是什么?Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。4.是否可以在static环境中访问非static变量?static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。5.Java支持的数据类型有哪些?什么是自动拆装箱?Java语言支持的8种基本数据类型是:byteshortintlongfloatdoublebooleanchar自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。6.Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。7.Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。8.Java支持多继承么?Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是java中的接口支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。9.接口和抽象类的区别是什么?Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。类可以实现很多个接口,但是只能继承一个抽象类类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。抽象类可以在不提供接口方法实现的情况下实现接口。Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。也可以参考JDK8中抽象类和接口的区别10.什么是值传递和引用传递?值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为,java内的传递都是值传递.11.进程和线程的区别是什么?进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。12.创建线程有几种不同的方式?你喜欢哪一种?为什么?有三种方式可以用来创建线程:继承Thread类实现Runnable接口应用程序可以使用Executor框架来创建线程池实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。13.概括的解释下线程的几种可用状态。1. 新建( new ):新创建了一个线程对象。2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。14.同步方法和同步代码块的区别是什么?区别:同步方法默认用this或者当前类class对象作为锁;同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;15.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。16.什么是死锁(deadlock)?两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。17.如何确保N个线程可以访问N个资源同时又不导致死锁?使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。18.Java集合类框架的基本接口有哪些?集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:Collection:代表一组对象,每一个对象都是它的子元素。Set:不包含重复元素的Collection。List:有顺序的collection,并且可以包含重复元素。Map:可以把键(key)映射到值(value)的对象,键不能重复。19.为什么集合类没有实现Cloneable和Serializable接口?克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。20.什么是迭代器(Iterator)?Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。21.Iterator和ListIterator的区别是什么?下面列出了他们的区别:Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。23.Java中的HashMap的工作原理是什么?Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。24.hashCode()和equals()方法的重要性体现在什么地方?Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。25.HashMap和Hashtable有什么区别?HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:HashMap允许键和值是null,而Hashtable不允许键或者值是null。Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。一般认为Hashtable是一个遗留的类。26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?下面列出了Array和ArrayList的不同点:Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。Array大小是固定的,ArrayList的大小是动态变化的。ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。数组适合于存储元素个数不变的数据,如果元素的个数是变化的,用ArrayList更好27.ArrayList和LinkedList有什么区别?ArrayList和LinkedList都实现了List接口,他们有以下的不同点:ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。28.Comparable和Comparator接口是干什么的?列出它们的区别。Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明已经存在的对象小于,等于,大于输入对象。Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。29.什么是Java优先级队列(Priority Queue)?PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。30.你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。31.如何权衡是使用无序的数组还是有序的数组?有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。32.Java集合类框架的最佳实践有哪些?根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。编程的时候接口优于实现。底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。33.Enumeration接口和Iterator接口的区别有哪些?Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。34.HashSet和TreeSet有什么区别?HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。35.Java中垃圾回收有什么目的?什么时候进行垃圾回收?垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。36.System.gc()和Runtime.gc()会做什么事情?这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。37.finalize()方法什么时候被调用?垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法 但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。38.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?不会,在下一个垃圾回收周期中,这个对象将是可被回收的。39.Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。永久代是用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类,永久代中一般包含:类的方法(字节码…)类名(Sring对象).class文件读到的常量信息class对象相关的对象列表和类型列表 (e.g., 方法对象的array).JVM创建的内部对象JIT编译器优化用的信息40.串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。41.在Java中,对象什么时候可以被垃圾回收?当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。42.JVM的永久代中会发生垃圾回收么?垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。43.Java中的两种异常类型是什么?他们有什么区别?Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。44.Java中Exception和Error有什么区别?Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。45.throw和throws有什么区别?throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。46.异常处理完成以后,Exception对象会发生什么变化?Exception对象会在下一个垃圾回收过程中被回收掉。47.finally代码块和finalize()方法有什么区别?无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。48.什么是Applet?java applet是能够被包含在HTML页面中并且能被启用了java的客户端浏览器执行的程序。Applet主要用来创建动态交互的web应用程序。49.解释一下Applet的生命周期applet可以经历下面的状态:Init:每次被载入的时候都会被初始化。Start:开始执行applet。Stop:结束执行applet。Destroy:卸载applet之前,做最后的清理工作。50.当applet被载入的时候会发生什么?首先,创建applet控制类的实例,然后初始化applet,最后开始运行。51.Applet和普通的Java应用程序有什么区别?applet是运行在启用了java的浏览器中,Java应用程序是可以在浏览器之外运行的独立的Java程序。但是,它们都需要有Java虚拟机。进一步来说,Java应用程序需要一个有特定方法签名的main函数来开始执行。Java applet不需要这样的函数来开始执行。最后,Java applet一般会使用很严格的安全策略,Java应用一般使用比较宽松的安全策略。52.Java applet有哪些限制条件?主要是由于安全的原因,给applet施加了以下的限制:applet不能够载入类库或者定义本地方法。applet不能在宿主机上读写文件。applet不能读取特定的系统属性。applet不能发起网络连接,除非是跟宿主机。applet不能够开启宿主机上其他任何的程序。53.什么是不受信任的applet?不受信任的applet是不能访问或是执行本地系统文件的Java applet,默认情况下,所有下载的applet都是不受信任的。54.从网络上加载的applet和从本地文件系统加载的applet有什么区别?当applet是从网络上加载的时候,applet是由applet类加载器载入的,它受applet安全管理器的限制。当applet是从客户端的本地磁盘载入的时候,applet是由文件系统加载器载入的。从文件系统载入的applet允许在客户端读文件,写文件,加载类库,并且也允许执行其他程序,但是,却通不过字节码校验。55.applet类加载器是什么?它会做哪些工作?当applet是从网络上加载的时候,它是由applet类加载器载入的。类加载器有自己的java名称空间等级结构。类加载器会保证来自文件系统的类有唯一的名称空间,来自网络资源的类有唯一的名称空间。当浏览器通过网络载入applet的时候,applet的类被放置于和applet的源相关联的私有的名称空间中。然后,那些被类加载器载入进来的类都是通过了验证器验证的。验证器会检查类文件格式是否遵守Java语言规范,确保不会出现堆栈溢出(stack overflow)或者下溢(underflow),传递给字节码指令的参数是正确的。56.applet安全管理器是什么?它会做哪些工作?applet安全管理器是给applet施加限制条件的一种机制。浏览器可以只有一个安全管理器。安全管理器在启动的时候被创建,之后不能被替换覆盖或者是扩展。57.什么是JDBC?JDBC是允许用户在不同数据库之间做选择的一个抽象层。JDBC允许开发者用JAVA写数据库应用程序,而不需要关心底层特定数据库的细节。58.解释下驱动(Driver)在JDBC中的角色。JDBC驱动提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection, Statement, PreparedStatement,CallableStatement, ResultSet和Driver。59.Class.forName()方法有什么作用?初始化参数指定的类,并且返回此类对应的Class 对象60.PreparedStatement比Statement有什么优势?PreparedStatements是预编译的,因此,性能会更好。同时,不同的查询参数值,PreparedStatement可以重用。61.什么时候使用CallableStatement?用来准备CallableStatement的方法是什么?CallableStatement用来执行存储过程。存储过程是由数据库存储和提供的。存储过程可以接受输入参数,也可以有返回结果。非常鼓励使用存储过程,因为它提供了安全性和模块化。准备一个CallableStatement的方法是:CallableStament.prepareCall();62.数据库连接池是什么意思?像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求。63.什么是RMI?Java远程方法调用(Java RMI)是Java API对远程过程调用(RPC)提供的面向对象的等价形式,支持直接传输序列化的Java对象和分布式垃圾回收。远程方法调用可以看做是激活远程正在运行的对象上的方法的步骤。RMI对调用者是位置透明的,因为调用者感觉方法是执行在本地运行的对象上的。64.RMI体系结构的基本原则是什么?RMI体系结构是基于一个非常重要的行为定义和行为实现相分离的原则。RMI允许定义行为的代码和实现行为的代码相分离,并且运行在不同的JVM上。65.RMI体系结构分哪几层?RMI体系结构分以下几层:存根和骨架层(Stub and Skeleton layer):这一层对程序员是透明的,它主要负责拦截客户端发出的方法调用请求,然后把请求重定向给远程的RMI服务。远程引用层(Remote Reference Layer):RMI体系结构的第二层用来解析客户端对服务端远程对象的引用。这一层解析并管理客户端对服务端远程对象的引用。连接是点到点的。传输层(Transport layer):这一层负责连接参与服务的两个JVM。这一层是建立在网络上机器间的TCP/IP连接之上的。它提供了基本的连接服务,还有一些防火墙穿透策略。66.RMI中的远程接口(Remote Interface)扮演了什么样的角色?远程接口用来标识哪些方法是可以被非本地虚拟机调用的接口。远程对象必须要直接或者是间接实现远程接口。实现了远程接口的类应该声明被实现的远程接口,给每一个远程对象定义构造函数,给所有远程接口的方法提供实现。67.java.rmi.Naming类扮演了什么样的角色?java.rmi.Naming类用来存储和获取在远程对象注册表里面的远程对象的引用。Naming类的每一个方法接收一个URL格式的String对象作为它的参数。68.RMI的绑定(Binding)是什么意思?绑定是为了查询找远程对象而给远程对象关联或者是注册以后会用到的名称的过程。远程对象可以使用Naming类的bind()或者rebind()方法跟名称相关联。69.Naming类的bind()和rebind()方法有什么区别?bind()方法负责把指定名称绑定给远程对象,rebind()方法负责把指定名称重新绑定到一个新的远程对象。如果那个名称已经绑定过了,先前的绑定会被替换掉。70.让RMI程序能正确运行有哪些步骤?为了让RMI程序能正确运行必须要包含以下几个步骤:编译所有的源文件。使用rmic生成stub。启动rmiregistry。启动RMI服务器。运行客户端程序。71.RMI的stub扮演了什么样的角色?远程对象的stub扮演了远程对象的代表或者代理的角色。调用者在本地stub上调用方法,它负责在远程对象上执行方法。当stub的方法被调用的时候,会经历以下几个步骤:初始化到包含了远程对象的JVM的连接。序列化参数到远程的JVM。等待方法调用和执行的结果。反序列化返回的值或者是方法没有执行成功情况下的异常。把值返回给调用者。72.什么是分布式垃圾回收(DGC)?它是如何工作的?DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。73.RMI中使用RMI安全管理器(RMISecurityManager)的目的是什么?RMISecurityManager使用下载好的代码提供可被RMI应用程序使用的安全管理器。如果没有设置安全管理器,RMI的类加载器就不会从远程下载任何的类。74.解释下Marshalling和demarshalling。当应用程序希望把内存对象跨网络传递到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。75.解释下Serialization和Deserialization。Java提供了一种叫做对象序列化的机制,他把对象表示成一连串的字节,里面包含了对象的数据,对象的类型信息,对象内部的数据的类型信息等等。因此,序列化可以看成是为了把对象存储在磁盘上或者是从磁盘上读出来并重建对象而把对象扁平化的一种方式。反序列化是把对象从扁平状态转化成活动对象的相反的步骤。76.什么是Servlet?Servlet是用来处理客户端请求并产生动态网页内容的Java类。Servlet主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。77.说一下Servlet的体系结构。所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者是间接实现这个接口,或者是继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,Servlet使用多线程可以并行的为多个请求服务。78.Applet和Servlet有什么区别?Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。79.GenericServlet和HttpServlet有什么区别?GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。继承自GenericServlet的Servlet应该要覆盖service()方法。最后,为了开发一个能用在网页上服务于使用HTTP协议请求的Servlet,你的Servlet必须要继承自HttpServlet。80.解释下Servlet的生命周期。对每一个客户端的请求,Servlet引擎载入Servlet,调用它的init()方法,完成Servlet的初始化。然后,Servlet对象通过为每一个请求单独调用service()方法来处理所有随后来自客户端的请求,最后,调用Servlet的destroy()方法把Servlet销毁。81.doGet()方法和doPost()方法有什么区别?doGet:GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。doPOST:POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。82.什么是Web应用程序?Web应用程序是对Web或者是应用服务器的动态扩展。有两种类型的Web应用:面向表现的和面向服务的。面向表现的Web应用程序会产生包含了很多种标记语言和动态内容的交互的web页面作为对请求的响应。而面向服务的Web应用实现了Web服务的端点(endpoint)。一般来说,一个Web应用可以看成是一组安装在服务器URL名称空间的特定子集下面的Servlet的集合。83.什么是服务端包含(Server Side Include)?服务端包含(SSI)是一种简单的解释型服务端脚本语言,大多数时候仅用在Web上,用servlet标签嵌入进来。SSI最常用的场景把一个或多个文件包含到Web服务器的一个Web页面中。当浏览器访问Web页面的时候,Web服务器会用对应的servlet产生的文本来替换Web页面中的servlet标签。84.什么是Servlet链(Servlet Chaining)?Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。85**.如何知道是哪一个客户端的机器正在请求你的Servlet?**ServletRequest类可以找出客户端机器的IP地址或者是主机名。getRemoteAddr()方法获取客户端主机的IP地址,getRemoteHost()可以获取主机名86.什么是cookie?session和cookie有什么区别?cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的cookie。下面列出了session和cookie的区别:无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但是,session仍然是能够工作的,因为客户端无法禁用服务端的session。在存储的数据量方面session和cookies也是不一样的。session能够存储任意的Java对象,cookie只能存储String类型的对象。87.什么是HTTP隧道?HTTP隧道是一种利用HTTP或者是HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把其他协议的请求掩盖成HTTP的请求就是HTTP隧道。88.sendRedirect()和forward()方法有什么区别?sendRedirect()方法会创建一个新的请求,而forward()方法只是把请求转发到一个新的目标上。重定向(redirect)以后,之前请求作用域范围以内的对象就失效了,因为会产生一个新的请求,而转发(forwarding)以后,之前请求作用域范围以内的对象还是能访问的。一般认为sendRedirect()比forward()要慢。89.JSP请求是如何被处理的?浏览器首先要请求一个以.jsp扩展名结尾的页面,发起JSP请求,然后,Web服务器读取这个请求,使用JSP编译器把JSP页面转化成一个Servlet类。需要注意的是,只有当第一次请求页面或者是JSP文件发生改变的时候JSP文件才会被编译,然后服务器调用servlet类,处理浏览器的请求。一旦请求执行结束,servlet会把响应发送给客户端。这里看下如何在JSP中获取请求参数。90.什么是JSP指令(Directive)?JSP中有哪些不同类型的指令?Directive是当JSP页面被编译成Servlet的时候,JSP引擎要处理的指令。Directive用来设置页面级别的指令,从外部文件插入数据,指定自定义的标签库。Directive是定义在 <%@ 和 %>之间的。下面列出了不同类型的Directive:包含指令(Include directive):用来包含文件和合并文件内容到当前的页面。页面指令(Page directive):用来定义JSP页面中特定的属性,比如错误页面和缓冲区。Taglib指令: 用来声明页面中使用的自定义的标签库。91.什么是JSP动作(JSP action)?JSP动作以XML语法的结构来控制Servlet引擎的行为。当JSP页面被请求的时候,JSP动作会被执行。它们可以被动态的插入到文件中,重用JavaBean组件,转发用户到其他的页面,或者是给Java插件产生HTML代码。下面列出了可用的动作:jsp:include-当JSP页面被请求的时候包含一个文件。jsp:useBean-找出或者是初始化Javabean。jsp:setProperty-设置JavaBean的属性。jsp:getProperty-获取JavaBean的属性。jsp:forward-把请求转发到新的页面。jsp:plugin-产生特定浏览器的代码。92.什么是Scriptlets?JSP技术中,scriptlet是嵌入在JSP页面中的一段Java代码。scriptlet是位于标签内部的所有的东西,在标签与标签之间,用户可以添加任意有效的scriplet。93**对于JSP,隐含对象是什么意思?有哪些隐含对象?**JSP隐含对象是页面中的一些Java对象,JSP容器让这些Java对象可以为开发者所使用。开发者不用明确的声明就可以直接使用他们。JSP隐含对象也叫做预定义变量。下面列出了JSP页面中的隐含对象:applicationpagerequestresponsesessionexceptionoutconfigpageContext————————————————
-
1)创建配置文件info.conf#数据库变量设置#数据库名dbbase='database'#用户名dbuser='user'#用户密码dbpass='password'#SQL执行次数para_num=2000#允许同时调起的gsql进程数,如果活跃进程大于此值则sleep直到进gsql进程数小于此值时,则继续增加并发pnum='1500'#sql文件中的sql语句数,用于计算tpssnum='5'#数据库集群的lvs地址lvsip='xxx.xxx.xxx.xxx'#sql文件名filename='filename'2.创建并发脚本#!/bin/bash. ./info.confc_path=$(cd "$(dirname "$0")";pwd)ctime=`date +%Y%m%d%H%M%S`fun() { for ((j=1;j<=$para_num;j++));do { echo "正在执行第$j个查询" >${c_path}/num gsql -p 25308 -U $dbuser -W $dbpass -d $dbbase -h $lvsip -f ${c_path}/${filename}.sql >>${c_path}/${filename}_$ctime.log 2>&1 & gsqlnum=`ps -ef|grep gsql |grep -v grep|wc -l` while [ "$gsqlnum" -gt "$pnum" ] do echo "当前并发量为:$gsqlnum,不再加并发" sleep 1 gsqlnum=`ps -ef|grep gsql |grep -v grep|wc -l` done }& done wait }main() { source /opt/huawei/Bigdata/mppdb/.mppdbgs_profile starttime=`date +"%Y-%m-%d %H:%M:%S"` echo "start time:$starttime" fun endtime=`date +"%Y-%m-%d %H:%M:%S"` echo "end time:$endtime" echo "sqlcount=$para_num">>${c_path}/Test_report_$ctime.log totaltime=$((`date +%s -d "$endtime"` - `date +%s -d "$starttime"`)) echo "totaltime=${totaltime}s">>${c_path}/Test_report_$ctime.log tps=`expr $para_num \* $snum / $totaltime` echo "tps=$tps sql/sec" >>${c_path}/Test_report_$ctime.log avgtime=`cat ${filename}_$ctime.log|grep '^Time:' |awk '{sum+=$2} END {print sum/NR}'` echo "avgtime=${avgtime}ms" >>${c_path}/Test_report_$ctime.log }mainexit 0执行后输出:totaltime:总执行时间tps:每秒执行的SQL数avgtime;: sql平均执行时间
-
【摘要】 NetBackup是Veritas公司软件产品,为各种平台提供完整而灵活的数据保护解决方案。这些平台包括Microsoft Windows、UNIX、Linux 等系统。利用NetBackup可以备份、归档和还原计算机上的文件、文件夹或目录以及卷或分区。当前DWS支持NBU介质备份恢复,本文介绍DWS对接NBU备份故障排除方法。部署方式假如已有3节点DWS集群,Roach(DWS备份工具)将本节点的集群数据通过TCP发送到远端NBU Media Server机器。每台NBU Media Server上面同时安装NBU Client,并部署Roach client组件,后者接收集群内Roach进程发来的备份数据,不落盘方式通过XBSA接口转发给本机的NBU Client,完成NBU备份。恢复流程也类似,只是数据流相反。在DWS备份过程中,一般故障主要出自以下三处:Roach agent: 即集群节点内,直接查看集群备份日志($GAUSSLOG/roach/)即可Roach client: 此插件主要负责数据收发,日志路径启动时通过-l参数指定,进入该路径查询即可NBU软件端: 可通过下文定位方式排查故障环境校验当进行NBU非侵入式备份时,考虑到集群备份过于重量,可以先通过指定小文件测试环境连通性,保证NBU配置gs_roach uploadmeta --media-destination 'nbu_policy' --metadata-destination '/home/Ruby/meta' --media-type NBU --backup-key '20200903_164332' --nbu-on-remote --media-server 192.168.243.65 --client-port 9000 注:--media-destination为NBU策略名称--backup-key为任一指定时间戳即可--media-server为任意一台部署了roach client插件的ip地址--client-port为roach client开放的端口--metadata-destination为上传指定文件路径,其中将测试上传文件重名名为metadata.tar.gz,并放置在/home/Ruby目录下,并非/home/Ruby/meta目录下如果能备份成功,则说明所连接的media server配置无问题,如果存在失败,则NBU端配置有问题,需要按照后续说明寻求原因。故障定义故障排除的第一步是定义问题。在NBU系统的安装、配置、运行过程中,出现了与正确预期不同的结果,即可认为是出现了故障;有时候,这要求我们知道正确的情况应该是什么样的。在NBU的交付和使用中常见的故障主要分为种:一是软件安装和配置阶段,比如软件安装不成功、对接不成功、某模块功能不可用等等,这一阶段的错误一般没有具体的错误码,需要结合交付人员的经验和系统日志进行排错,这种故障属于一次性的故障,在排除之后再次出现的可能性很小;二是在系统部署完成后,数据备份业务上线、备份和恢复任务执行时报错,比如接入client失败、存储单元写入数据失败、找不到client服务器等等;这种故障console会提供错误码(error code),维护人员可以根据错误进行初步的定位,这种故障属于日常性的故障,和环境中多种因素有关,备份系统自身之外的业务环境发生细微的变化都有可能导致故障的出现。故障排除过程要排除问题,必须知道发生了什么错误。错误消息通常是指出哪里出现故障的手段。所以,我们要做的第一件事就是查找错误消息。如果在界面上没有看到错误消息,但仍怀疑有问题,请检查报告和日志。NetBackup 提供了广泛的报告和日志记录工具,这些工具可提供错误消息,直接指出解决方案。日志还可显示什么运行良好以及当发生问题时 NetBackup 正在执行什么操作。综上,NBU备份与恢复故障排除过程如下:1、确认服务器和client运行的是受支持的操作系统或应用版本;具体信息参看NBU兼容性列表;2、复现故障,获取故障信息;获取信息的渠道有错误码、Job Details、日志等;3、根据获取的信息进行故障定位和排除;故障排除方法使用状态码每一个备份和恢复任务都是一个activity,在activity monitor一栏中可以监控到它们。由任务监视看出该任务的ID、执行何种操作、状态、返回值、Server和Client是谁、通过哪一个Policy和Schedule去执行的。具体可显示多长时间的任务,要看NetBackup全局属性中的设置。每个任务有以下几个状态:Queued 任务正在排队Active 任务正在执行Done 任务执行完毕在activity的执行过程中,每一个任务结果都对应着一个状态代码,0代表成功,非0代表故障。返回值是一个非常有用的参数,通过返回值,可以通过错误代码查找手册中建议的相关调整建议,这对于问题检查和性能调整是非常有用的。页面中获取位置如下:以下链接提供了NBU备份任务status code list:https://www.veritas.com/content/support/en_US/doc/44037985-127664609-0/v15096675-127664609根据获取到的status code可以初步定位错误原因使用Job details与状态码类似,Job details与activity也是一对一;不同的是,Job details比状态码提供的信息更多,对于常见的故障,使用Job details可以完成故障的原因定位和排除。双击一个activity,选择detailed status,在status一栏即可获取更多的细节信息。找到关键错误信息(通常是红色字体或红色字体的上下文),提炼出关键字,在google上搜索,互联网上有大量的相同错误场景和解决办法。使用日志以上使用状态码和Job details进行故障排除的办法停留在初级阶段,通常只对简单故障有效;对于复杂问题,如果解决不了则需要搜集日志进行分析。在NBU系统中,日志级别共分为6级,分别为0-5,以下为日志级别对应的要记录的信息:0:非常重要的少量诊断消息和调试消息1:该级别增加详细的诊断消息和调试消息2:增加进度消息3:增加提示性转储消息4:增加功能进入和退出消息5:最详细的信息:记录所有信息日志等级调整方式如下:1、console界面调整2、vi /usr/openv/netbackup/bp.conf, 在末尾调加如下配置VERBOSE = 5NBU系统针对每一个进程都有一个独立的目录来存放,但是在默认情况下不创建,所有如果想要搜集这些日志,工程师需要手动创建这些目录。目录格式为/usr/openv/netbackup/logs/进程名;以bpcd程序为例,执行以下命令创建子目录:mkdir /usr/openv/netbackup/logs/bpcd或者使用NBU提供的批量创建脚本,一键创建所有日志目录,执行以下命令:sh /usr/openv/netbackup/logs/mklogdir在搜集日志时,NBU针对性地为每个进程创建一个日志子目录,来实现进程级别的日志分析,那么我们需要先知道NBU常用的进程有哪些:admin:管理命令。bpbrm:NetBackup 备份和还原管理器。bpcd:NetBackup client后台驻留程序或管理器。bpdm:NetBackup 磁盘管理器。bpdbm:NetBackup 数据库管理器。此进程仅在主服务器上运行。bprd:NetBackup 请求管理器,对客户机和备份、恢复、归档等管理请求作出响应。vnetd:Veritas 网络后台驻留程序。bpbackup:在UNIX client上,当用户启动备份时,此程序与主服务器上的bprd通信。在获取了日志之后,在各个文件中搜索fail、error、can not、freeze等关键字,进行故障原因定位NBU常用维护命令用命令行启动netbackup服务进程/usr/openv/netbackup/bin/bp.start_all用命令行停止netbackup服务进程/usr/openv/netbackup/bin/bp.kill_all用命令行清除host缓存/usr/openv/netbackup/bin/bpclntcmd -clear_host_cache # 清除缓存 cd /usr/openv/var/host_cache/ # 清除临时文件 rm –rf tmp mkdir tmp mv * tmp用命令行检测master和client连通性/usr/openv/netbackup/bin/admincmd/bptestbpcd -client client_hostname若可以连通,返回结果类似如下:NBU master server与NBU client 通信问题在client和master server上互相telnet对方的备份管理平面IP的1556、1372、13782三个端口,确认client服务器与master server通信正常netstat –an | grep 1556 netstat –an | grep 1372 netstat –an | grep 13782检查NBU服务及进程/usr/openv/netbackup/bin/./bpps -xMedia server不是认证的主机此为client上对media server的信任配置问题。在console上点击host properties>client,找到故障客户端,双击client,在弹出界面点击servers一栏,在additional server配置中添加media server的主机名存储单元不可用出现“存储单元不可用”故障信息可能有以下几种情况:1、存储单元已满2、此存储单元上处于排队状态的备份任务过多3、client与存储单元归属的media server无法通信 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~原文链接:https://bbs.huaweicloud.com/blogs/265919
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签