• [调优工具] 【Hyper Tuner调优实践 21】基于Java性能分析工具的GC日志调优实践
    1      调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具。本文使用Java性能优化工具对运行中的Java程序进行在线分析和采样分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2      环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6调优工具华为鲲鹏性能优化工具 V2.3.T103      前提条件服务器和操作系统正常运行。PC端已经安装SSH远程登录工具。需要优化的Java程序且程序开启GC日志打印。4      调优思路 使用Java性能优化工具对Java进程GC日志进行在线采集或离线上传,解析获取的GC日志。 针对解析后得到的优化建议项进行调优。 观察优化后的Java程序日志,判断对应问题是否改善。 5      调优过程5.1      获取GC日志5.1.1        对程序进行在线分析,在GC页签下GC日志页面点击执行GC日志采集,获取日志并进行解析。5.2      查看优化建议项当前GC日志共有五项优化建议:依次查看对应优化建议,并根据建议项进行信息修改。5.2.1        元空间扩容导致的GC优化jdk8以后移除永久区,使用本地内存来存储类元数据信息并称之为元空间(Metaspace)。持续的元空间垃圾回收说明:可能存在类、类加载器导致的内存泄漏或是大小设置不合适。根据优化建议对代码进行排查,发现代码中存在每次调用循环动态创建并加载类。针对本例:可减循环次数,减少类的加载;将classes对象设为单例模式避免重复调用。对于无法进行代码优化的案例结合成因分析,查看因元数据产生的GC次数及时间占比是否偏高,若想降低可调高-XX:MetaspaceSize,少因扩容造成的GC。5.2.2        疏散失败当给出疏散失败时,表示存在GC幸存区或Old区空间不足,可增大响应空间避免出现疏散失败。当空间无法增加时可尝试调整如下选项:1、调大-XX:G1ReservePercent(默认值10),设置堆老年代预留内存晋升,以降低提升失败的可能性;降低-XX:InitiatingHeapOccupancyPercent阈值(默认45);3、增加并行标记线程的数目,调大-XX:ParallelGCThreads参数(默认值随运行平台不同而不同)。可在优化后重新执行GC日志采集,查看疏散失败优化建议是否触发。或在日志中统计单位时间内疏散失败的比例。5.2.3        发生巨型对象导致的GC当发生巨型对象分配时(大于region size的50%),G1会找出一个连续的可用分区集合,这样就能汇总出足够的内存来容纳巨型对象。如果没有足够的连续可用空间,G1就会启动一次Full GC 来压缩Java堆空间。巨对象分配后对应region不再存储其它对象造成了一定的空间浪费。通过调大-XX:G1HeapRegionSize可减少因巨对象空间不足导致的Full GC且能提升内存利用率。可通过GC成因分析查看G1 Humongous Allocation触发的GC占比统计看巨对象触发的GC是否由改善。5.2.4        发生Full GC当空间即将耗尽或分配速度无法追上回收速度时就会触发Full GC,当有Full GC发生代表性能可能不足。除关注程序本身是否可优化或提升机器性能(增加内存,提升cpu性能)外,还可以尝试调整相关参数提高GC效率来降低Full GC发生的可能性:1、增加堆内存(Xmx)大小,让G1有更多的时间去完成 Concurrent Marking。2、适当加大 -XX:ConcGCThreads 选项的值,增加并发标记的线程数。3、通过增加 -XX:G1ReservePercent(默认10%)的选项值,增加 G1 在 IHOP 分析过程中所需要的内存空间。4、通过设置 -XX:-G1UseAdaptiveIHOP 的选项值禁用自适应 IHOP 分析机制,通过调小 -XX:InitiatingHeapOccupancyPercent 的选项(默认值为 45)值达到提前触发GC标记周期的目的。可对比优化前与优化后的GC活动细化分析数据下的Full GC统计数据,通过对Full GC平均间隔时间对比可确定Full GC频率是否降低。5.2.5        发生System.GC()导致的GC程序中不建议显示调用System.GC()强制触发Full GC。建议排查代码将相关代码移除。或使用-XX:+DisableExplicitGC忽略System.GC()调用。若为系统级别或框架级别的调用无法禁用,可开启-XX:+ExplicitGCInvokesConcurrent,Full GC 会变成 initial-mark。检验优化效果可对优化后程序进行日志采集查看优化建议中是否还有system.GC()的优化建议。或查看GC日志看system.GC()触发日志是否存在,若存在看是否为initial-mark阶段6      实践总结Java中通过查看GC日志较难直接定位问题,故平台提供的GC日志解析后优化建议,通过优化建议可较为便捷的对代码进行排查或修改启动参数。重启进程并在运作一段时间后再次采集解析GC日志,通过对比前后日志吞吐量、GC平均暂停时间、活动细化分析-平均间隔时间等数据的变化,从而确定GC效率是否已经改善。针对GC日志的调优不建议作为优先使用方案,可作为在程序自身调优已达瓶且无更多调优手段时的尝试。据GC优化建议对程序进行优化后无明显改善的,建议:升级机器配置以适应业务需求!
  • [技术干货] JDK、
    rmic功能说明:   rmic 为远程对象生成 stub 和 skeleton。 语法:   rmic [ options ] package-qualified-class-name(s)补充说明:   rmic 编译器根据编译后的 Java 类(含有远程对象实现)名,为远程对象生成 stub 和 skeleton(远程对象是指实现 java.rmi.Remote 接口的对象)。在 rmic 命令中所给的类必须是经 javac 命令成功编译且是完全包限定的类。 命令选项   -classpath[路径] 指定 rmic 用于查询类的路径。如果设置了该选项,它将覆盖缺省值或 CLASSPATH 环境变量。目录用冒号分隔。   -d[目录] 指定类层次的根目录。此选项可用来指定 stub 和 skeleton 文件的目标目录。   -depend 使编译器考虑重新编译从其它类引用的类。 一般来说,它只重新编译从源代码引用的遗漏或过期的类。  -g 允许生成调试表格。调试表格含有行号和局部变量的有关信息,即 Java 调试工具所使用的信息。缺省情况下,只生成行号。  -J 与 -D 选项联用,它将紧跟其后的选项( -J 与 -D 之间无空格)传给 java 解释器。  -keepgenerated 为 stub 和 skeleton 文件保留所生成的 .java 源文件,并将这些源文件写到与 .class 文件相同的目录中,如果要指定目录,则使用 -d 选项。  -nowarn 关闭警告。如果使用该选项,则编译器不输出任何警告信息。  -show 显示 rmic 编译器的 GUI(图形用户界面)。输入一个或多个包限定类名(以空格分隔),并按回车键或“显示”按钮,创建 stub 和 skeleton。  -vcompat (缺省值)创建与 JDK 1.1 和 1.2 stub 协议版本都兼容的 stub 和 skeleton。  -verbose 使编译器和链接器输出关于正在编译哪些类和正在加载哪些类文件的信息。  -v1.1 创建 JDK 1.1 stub 协议版本的 stub 和 skeleton。  -v1.2 只创建 JDK 1.2 stub 协议版本的 stub。rmid功能说明:   rmid 启动激活系统守护进程,以便能够在 Java 虚拟机上注册和激活对象。语法:   rmid [-port port] [-log dir]补充说明:   rmid 工具启动激活系统守护进程。必须先启动激活系统守护进程,才能向激活系统注册可被激活的对象或在 Java 虚拟机上激活可被激活的对象。命令选项   -C<某些命令行选项> 指定一个选项,在创建每个 rmid 的子守护进程(激活组)时,该选项以命令行参数的形式传给该子守护进程。  -log[目录] 指定目录的名称,激活系统守护进程在该目录中写入其数据库及相关信息。缺省状态下,将在执行 rmid 命令的目录中创建一个 log 目录。  -port[端口] 指定 rmid 的注册服务程序所使用的端口。激活系统守护进程将 ActivationSystem 与该注册服务程序中的名称java.rmi.activation.ActivationSystem 捆绑在一起。  -stop 停止 -port 选项所指定端口上的当前 rmid 调用。若未指定端口,则将停止在端口 1098 上运行的 rmid。rmiregistry功能说明:   rmiregistry 命令可在当前主机的指定端口上启动远程对象注册服务程序。语法:   rmiregistry [port]补充说明:   rmiregistry 命令在当前主机的指定 port 上创建并启动远程对象注册服务程序。如果省略 port,则注册服务程序将在 1099 端口上启动。rmiregistry 命令不产生任何输出而且一般在后台运行。远程对象注册服务程序是自举命名服务。主机上的 RMI 服务器将利用它将远程对象绑定到名字上。客户机即可查询远程对象并进行远程方法调用。注册服务程序一般用于定位应用程序需调用其方法的第一个远程对象。该对象反过来对各应用程序提供相应的支持,用于查找其它对象。
  • [全栈开发者] 【Java编程创造营】第二阶段·问答官排位赛活动帖
    为了巩固大家的学习成果,紧跟学习进度为大家提供学习交流的平台,推出问答官排位赛活动按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦打卡时间2021.07.06-2021.08.09 23:59  参与方式:用户在本帖里发布自己在Java学习中产生的疑惑或实践问题,其他用户可通过在楼层下评论参与回答。  参与规则:1. 同一ID不可自问自答;2. 同一ID可回复其他同一ID问题数量需≤3次,如,A最多可回答B的三个问题,但A可回答B、C、D、E、F等多人次问题。  活动奖励:◎每个有效提问可获得5积分。上限为2个问题,最高10分。◎每个有效回答可获得2积分,上限为2个回答,最高4分。不得灌水,不得与他人显示,问题具有意义,占楼无效。有效提问示例:无效提问示例:◎最终会全部每阶段参与互动的用户中,抽取3位幸运用户,送出旅行本套装*1活动奖励点击了解Java编程创造营专属福利活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成提交问答官问题,可获得的积分上限为5积分/每有效问题(最高10分)2积分/每有答复(最高4分);3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761&page=1&extra=#pid1161471
  • [全栈开发者] 【Java编程创造营】第二阶段·技术文章征集帖
    为了巩固大家的学习成果,紧跟学习进度请将需要完成的相关技术文章内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59征集要求1.本学习阶段任意时间内,在【华为云】-【博客】发表与Java相关的任意博客内容,可以是开发技术技巧,可以是开发心得体会,不可以发布课程笔记作为博文,将文章链接回复至本帖内2.文章需为原创,在发布博客时,需要勾选文章创作类型:原创文章一项。用户承诺,发表的文章不存在任何知识产权问题并自行承担全部责任3.文章字数≥600字,图文并茂,不得抄袭网站课程,需写自己的理解和技术分享4.回复格式:华为云ID+博客文章链接5.如果曾在外部博客发表过的文章,可将文章迁移到华为云博客即可6.技术文章由华为技术专家评审,评审通过会对应的积分,不过专家会给出对应的评语且积分为0奖励方式每阶段每篇有效博客文章可获得5积分,每阶段获取上限10分,每阶段评选1篇最佳博文,奖励定制机械键盘~ 全系列活动奖励本次活动不仅免费跟大牛学习技术,更有积分大礼等着大家~每次有效提交的学习任务都会得到对应积分,赢取智能硬件,还有华为无线耳机、机械键盘等你拿!点击了解Java编程创造营专属福利活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成客观题打卡任务,可获得的积分上限为5分/每篇;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [全栈开发者] 【Java编程创造营】第二阶段·每周学习笔记分享打卡贴
    为了巩固大家的学习成果,紧跟学习进度请按需要完成的每周读书笔记内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59打卡要求在本帖中,回复每周读书笔记和心得 ↓回复格式:华为云ID+第几周笔记+笔记内容,并分享些读书心得(字数≥200字)回复示例如下:奖励方式每周每次有效打卡可获得2积分阶段学习将以积分排行榜的形式,对符合条件的小伙伴进行奖励!点击了解Java编程创造营专属福利活动注意事项 由于本帖回复内容为随堂测试结果,已设置回帖仅版主可见1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成提交学习笔记任务,可获得的积分上限为2分/每周;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [全栈开发者] 【Java编程创造营】第二阶段·每章随堂测验打卡帖
    【Java编程创造营】第二阶段正在进行中为了巩固大家的学习成果,紧跟学习进度请将需要完成的每章随堂测验内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59打卡要求在本帖中,回复对应章节的随堂测验并提交截图 ↓回复格式:自己的华为云ID+课堂章节序号+课程完成截图(露出右上角华为云ID,随堂测验为绿色对勾完成状态)回复示例:华为云帐号:grandmaster+第一章 测试题截图奖励方式每章节每次有效打卡可获得2积分阶段学习将以积分排行榜的形式,对符合条件的小伙伴进行奖励!附加福利:每周在本周内提交随堂测试的用户中,抽取1名幸运奖励华为云定制鼠标*1点击了解Java编程创造营专属福利活动注意事项 由于本帖回复内容为随堂测试结果,已设置回帖仅版主可见1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成每章习题打卡任务,可获得的积分上限为2分/每章节;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [技术干货] 快来,这里有23种设计模式的Go语言实现(一)
    【摘要】 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。前言从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题。在当下,如果你不会一点设计模式,都不好意思说自己是一个合格的程序员。设计模式通常被定义为:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。从定义上看,设计模式其实是一种经验的总结,是针对特定问题的简洁而优雅的解决方案。既然是经验总结,那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。然而,学习设计模式的最高境界是习得其中解决问题所用到的思想,当你把它们的本质思想吃透了,也就能做到即使已经忘掉某个设计模式的名称和结构,也能在解决特定问题时信手拈来。好的东西有人吹捧,当然也会招黑。设计模式被抨击主要因为以下两点:1、设计模式会增加代码量,把程序逻辑变得复杂。这一点是不可避免的,但是我们并不能仅仅只考虑开发阶段的成本。最简单的程序当然是一个函数从头写到尾,但是这样后期的维护成本会变得非常大;而设计模式虽然增加了一点开发成本,但是能让人们写出可复用、可维护性高的程序。引用《软件设计的哲学》里的概念,前者就是战术编程,后者就是战略编程,我们应该对战术编程Say No!(请移步《专家教你系列<三>:写出的代码复杂度太高?看下专家怎么说》)2、滥用设计模式。这是初学者最容易犯的错误,当学到一个模式时,恨不得在所有的代码都用上,从而在不该使用模式的地方刻意地使用了模式,导致了程序变得异常复杂。其实每个设计模式都有几个关键要素:适用场景、解决方法、优缺点。模式并不是万能药,它只有在特定的问题上才能显现出效果。所以,在使用一个模式前,先问问自己,当前的这个场景适用这个模式吗?《设计模式》一书的副标题是“可复用面向对象软件的基础”,但并不意味着只有面向对象语言才能使用设计模式。模式只是一种解决特定问题的思想,跟语言无关。就像Go语言一样,它并非是像C++和Java一样的面向对象语言,但是设计模式同样适用。本系列文章将使用Go语言来实现GoF提出的23种设计模式,按照创建型模式(Creational Pattern)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)三种类别进行组织,文本主要介绍其中的创建型模式。单例模式(Singleton Pattern)简述单例模式算是23中设计模式里最简单的一个了,它主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。在程序设计中,有一些对象通常我们只需要一个共享的实例,比如线程池、全局缓存、对象池等,这种场景下就适合使用单例模式。但是,并非所有全局唯一的场景都适合使用单例模式。比如,考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。如何判断一个对象是否应该被建模成单例?通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,这个对象是一个中心点吗?Go实现在对某个对象实现单例模式时,有两个点必须要注意:(1)限制调用者直接实例化该对象;(2)为该对象的单例提供一个全局唯一的访问方法。对于C++/Java而言,只需把类的构造函数设计成私有的,并提供一个static方法去访问该类点唯一实例即可。但对于Go语言来说,即没有构造函数的概念,也没有static方法,所以需要另寻出路。我们可以利用Go语言package的访问规则来实现,将单例结构体设计成首字母小写,就能限定其访问范围只在当前package下,模拟了C++/Java中的私有构造函数;再在当前package下实现一个首字母大写的访问函数,就相当于static方法的作用了。在实际开发中,我们经常会遇到需要频繁创建和销毁的对象。频繁的创建和销毁一则消耗CPU,二则内存的利用率也不高,通常我们都会使用对象池技术来进行优化。考虑我们需要实现一个消息对象池,因为是全局的中心点,管理所有的Message实例,所以将其实现成单例,实现代码如下: package msgpool ... // 消息池 type messagePool struct { pool *sync.Pool } // 消息池单例 var msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } // 访问消息池单例的唯一方法 func Instance() *messagePool { return msgPool } // 往消息池里添加消息 func (m *messagePool) AddMsg(msg *Message) { m.pool.Put(msg) } // 从消息池里获取消息 func (m *messagePool) GetMsg() *Message { return m.pool.Get().(*Message) } ...测试代码如下: package test ... func TestMessagePool(t *testing.T) { msg0 := msgpool.Instance().GetMsg() if msg0.Count != 0 { t.Errorf("expect msg count %d, but actual %d.", 0, msg0.Count) } msg0.Count = 1 msgpool.Instance().AddMsg(msg0) msg1 := msgpool.Instance().GetMsg() if msg1.Count != 1 { t.Errorf("expect msg count %d, but actual %d.", 1, msg1.Count) } } // 运行结果 === RUN TestMessagePool --- PASS: TestMessagePool (0.00s) PASS以上的单例模式就是典型的“饿汉模式”,实例在系统加载的时候就已经完成了初始化。对应地,还有一种“懒汉模式”,只有等到对象被使用的时候,才会去初始化它,从而一定程度上节省了内存。众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验锁来优化。对于“懒汉模式”,Go语言有一个更优雅的实现方式,那就是利用sync.Once,它有一个Do方法,其入参是一个方法,Go语言会保证仅仅只调用一次该方法。 // 单例模式的“懒汉模式”实现 package msgpool ... var once = &sync.Once{} // 消息池单例,在首次调用时初始化 var msgPool *messagePool // 全局唯一获取消息池pool到方法 func Instance() *messagePool { // 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次 once.Do(func() { msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } }) return msgPool } ...建造者模式(Builder Pattern)简述在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。这种情况下,创建这个复杂对象就会变得很繁琐。对于C++/Java而言,最常见的表现就是构造函数有着长长的参数列表: MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)而对于Go语言来说,最常见的表现就是多层的嵌套实例化: obj := &MyObject{ Field1: &Field1 { Param1: &Param1 { Val: 0, }, Param2: &Param2 { Val: 1, }, ... }, Field2: &Field2 { Param3: &Param3 { Val: 2, }, ... }, ... }上述的对象创建方法有两个明显的缺点:(1)对对象使用者不友好,使用者在创建对象时需要知道的细节太多;(2)代码可读性很差。针对这种对象成员较多,创建对象逻辑较为繁琐的场景,就适合使用建造者模式来进行优化。建造者模式的作用有如下几个:1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。3、对多个对象复用同样的对象创建逻辑。其中,第1和第2点比较常用,下面对建造者模式的实现也主要是针对这两点进行示例。Go实现考虑如下的一个Message结构体,其主要有Header和Body组成: package msg ... type Message struct { Header *Header Body *Body } type Header struct { SrcAddr string SrcPort uint64 DestAddr string DestPort uint64 Items map[string]string } type Body struct { Items []string } ...如果按照直接的对象创建方式,创建逻辑应该是这样的: // 多层的嵌套实例化 message := msg.Message{ Header: &msg.Header{ SrcAddr: "192.168.0.1", SrcPort: 1234, DestAddr: "192.168.0.2", DestPort: 8080, Items: make(map[string]string), }, Body: &msg.Body{ Items: make([]string, 0), }, } // 需要知道对象的实现细节 message.Header.Items["contents"] = "application/json" message.Body.Items = append(message.Body.Items, "record1") message.Body.Items = append(message.Body.Items, "record2")虽然Message结构体嵌套的层次不多,但是从其创建的代码来看,确实存在对对象使用者不友好和代码可读性差的缺点。下面我们引入建造者模式对代码进行重构: package msg ... // Message对象的Builder对象 type builder struct { once *sync.Once msg *Message } // 返回Builder对象 func Builder() *builder { return &builder{ once: &sync.Once{}, msg: &Message{Header: &Header{}, Body: &Body{}}, } } // 以下是对Message成员对构建方法 func (b *builder) WithSrcAddr(srcAddr string) *builder { b.msg.Header.SrcAddr = srcAddr return b } func (b *builder) WithSrcPort(srcPort uint64) *builder { b.msg.Header.SrcPort = srcPort return b } func (b *builder) WithDestAddr(destAddr string) *builder { b.msg.Header.DestAddr = destAddr return b } func (b *builder) WithDestPort(destPort uint64) *builder { b.msg.Header.DestPort = destPort return b } func (b *builder) WithHeaderItem(key, value string) *builder { // 保证map只初始化一次 b.once.Do(func() { b.msg.Header.Items = make(map[string]string) }) b.msg.Header.Items[key] = value return b } func (b *builder) WithBodyItem(record string) *builder { b.msg.Body.Items = append(b.msg.Body.Items, record) return b } // 创建Message对象,在最后一步调用 func (b *builder) Build() *Message { return b.msg }测试代码如下: package test ... func TestMessageBuilder(t *testing.T) { // 使用消息建造者进行对象创建 message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() if message.Header.SrcAddr != "192.168.0.1" { t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr) } if message.Body.Items[0] != "record1" { t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0]) } } // 运行结果 === RUN TestMessageBuilder --- PASS: TestMessageBuilder (0.00s) PASS从测试代码可知,使用建造者模式来进行对象创建,使用者不再需要知道对象具体的实现细节,代码可读性也更好。工厂方法模式(Factory Method Pattern)简述工厂方法模式跟上一节讨论的建造者模式类似,都是将对象创建的逻辑封装起来,为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别,建造者模式更常用于需要传递多个参数来进行实例化的场景。使用工厂方法来创建对象主要有两个好处:1、代码可读性更好。相比于使用C++/Java中的构造函数,或者Go中的{}来创建对象,工厂方法因为可以通过函数名来表达代码含义,从而具备更好的可读性。比如,使用工厂方法productA := CreateProductA()创建一个ProductA对象,比直接使用productA := ProductA{}的可读性要好。2、与使用者代码解耦。很多情况下,对象的创建往往是一个容易变化的点,通过工厂方法来封装对象的创建过程,可以在创建逻辑变更时,避免霰弹式修改。工厂方法模式也有两种实现方式:(1)提供一个工厂对象,通过调用工厂对象的工厂方法来创建产品对象;(2)将工厂方法集成到产品对象中(C++/Java中对象的static方法,Go中同一package下的函数)Go实现考虑有一个事件对象Event,分别有两种有效的时间类型Start和End: package event ... type Type uint8 // 事件类型定义 const ( Start Type = iota End ) // 事件抽象接口 type Event interface { EventType() Type Content() string } // 开始事件,实现了Event接口 type StartEvent struct{ content string } ... // 结束事件,实现了Event接口 type EndEvent struct{ content string } ...1、按照第一种实现方式,为Event提供一个工厂对象,具体代码如下: package event ... // 事件工厂对象 type Factory struct{} // 更具事件类型创建具体事件 func (e *Factory) Create(etype Type) Event { switch etype { case Start: return &StartEvent{ content: "this is start event", } case End: return &EndEvent{ content: "this is end event", } default: return nil } }测试代码如下: package test ... func TestEventFactory(t *testing.T) { factory := event.Factory{} e := factory.Create(event.Start) if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = factory.Create(event.End) if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEventFactory --- PASS: TestEventFactory (0.00s) PASS2、按照第二种实现方式,分别给Start和End类型的Event单独提供一个工厂方法,代码如下: package event ... // Start类型Event的工厂方法 func OfStart() Event { return &StartEvent{ content: "this is start event", } } // End类型Event的工厂方法 func OfEnd() Event { return &EndEvent{ content: "this is end event", } }测试代码如下: package event ... func TestEvent(t *testing.T) { e := event.OfStart() if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = event.OfEnd() if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEvent --- PASS: TestEvent (0.00s) PASS抽象工厂模式(Abstract Factory Pattern)简述在工厂方法模式中,我们通过一个工厂对象来创建一个产品族,具体创建哪个产品,则通过swtich-case的方式去判断。这也意味着该产品组上,每新增一类产品对象,都必须修改原来工厂对象的代码;而且随着产品的不断增多,工厂对象的职责也越来越重,违反了单一职责原则。抽象工厂模式通过给工厂类新增一个抽象层解决了该问题,如上图所示,FactoryA和FactoryB都实现·抽象工厂接口,分别用于创建ProductA和ProductB。如果后续新增了ProductC,只需新增一个FactoryC即可,无需修改原有的代码;因为每个工厂只负责创建一个产品,因此也遵循了单一职责原则。Go实现考虑需要如下一个插件架构风格的消息处理系统,pipeline是消息处理的管道,其中包含了input、filter和output三个插件。我们需要实现根据配置来创建pipeline ,加载插件过程的实现非常适合使用工厂模式,其中input、filter和output三类插件的创建使用抽象工厂模式,而pipeline的创建则使用工厂方法模式。各类插件和pipeline的接口定义如下: package plugin ... // 插件抽象接口定义 type Plugin interface {} // 输入插件,用于接收消息 type Input interface { Plugin Receive() string } // 过滤插件,用于处理消息 type Filter interface { Plugin Process(msg string) string } // 输出插件,用于发送消息 type Output interface { Plugin Send(msg string) } package pipeline ... // 消息管道的定义 type Pipeline struct { input plugin.Input filter plugin.Filter output plugin.Output } // 一个消息的处理流程为 input -> filter -> output func (p *Pipeline) Exec() { msg := p.input.Receive() msg = p.filter.Process(msg) p.output.Send(msg) }接着,我们定义input、filter、output三类插件接口的具体实现: package plugin ... // input插件名称与类型的映射关系,主要用于通过反射创建input对象 var inputNames = make(map[string]reflect.Type) // Hello input插件,接收“Hello World”消息 type HelloInput struct {} ​ func (h *HelloInput) Receive() string { return "Hello World" } // 初始化input插件映射关系表 func init() { inputNames["hello"] = reflect.TypeOf(HelloInput{}) } package plugin ... // filter插件名称与类型的映射关系,主要用于通过反射创建filter对象 var filterNames = make(map[string]reflect.Type) // Upper filter插件,将消息全部字母转成大写 type UpperFilter struct {} ​ func (u *UpperFilter) Process(msg string) string { return strings.ToUpper(msg) } // 初始化filter插件映射关系表 func init() { filterNames["upper"] = reflect.TypeOf(UpperFilter{}) } package plugin ... // output插件名称与类型的映射关系,主要用于通过反射创建output对象 var outputNames = make(map[string]reflect.Type) // Console output插件,将消息输出到控制台上 type ConsoleOutput struct {} ​ func (c *ConsoleOutput) Send(msg string) { fmt.Println(msg) } // 初始化output插件映射关系表 func init() { outputNames["console"] = reflect.TypeOf(ConsoleOutput{}) }然后,我们定义插件抽象工厂接口,以及对应插件的工厂实现: package plugin ... // 插件抽象工厂接口 type Factory interface { Create(conf Config) Plugin } // input插件工厂对象,实现Factory接口 type InputFactory struct{} // 读取配置,通过反射机制进行对象实例化 func (i *InputFactory) Create(conf Config) Plugin { t, _ := inputNames[conf.Name] return reflect.New(t).Interface().(Plugin) } // filter和output插件工厂实现类似 type FilterFactory struct{} func (f *FilterFactory) Create(conf Config) Plugin { t, _ := filterNames[conf.Name] return reflect.New(t).Interface().(Plugin) } type OutputFactory struct{} func (o *OutputFactory) Create(conf Config) Plugin { t, _ := outputNames[conf.Name] return reflect.New(t).Interface().(Plugin) }最后定义pipeline的工厂方法,调用plugin.Factory抽象工厂完成pipelien对象的实例化: package pipeline ... // 保存用于创建Plugin的工厂实例,其中map的key为插件类型,value为抽象工厂接口 var pluginFactories = make(map[plugin.Type]plugin.Factory) // 根据plugin.Type返回对应Plugin类型的工厂实例 func factoryOf(t plugin.Type) plugin.Factory { factory, _ := pluginFactories[t] return factory } // pipeline工厂方法,根据配置创建一个Pipeline实例 func Of(conf Config) *Pipeline { p := &Pipeline{} p.input = factoryOf(plugin.InputType).Create(conf.Input).(plugin.Input) p.filter = factoryOf(plugin.FilterType).Create(conf.Filter).(plugin.Filter) p.output = factoryOf(plugin.OutputType).Create(conf.Output).(plugin.Output) return p } // 初始化插件工厂对象 func init() { pluginFactories[plugin.InputType] = &plugin.InputFactory{} pluginFactories[plugin.FilterType] = &plugin.FilterFactory{} pluginFactories[plugin.OutputType] = &plugin.OutputFactory{} }测试代码如下: package test ... func TestPipeline(t *testing.T) { // 其中pipeline.DefaultConfig()的配置内容见【抽象工厂模式示例图】 // 消息处理流程为 HelloInput -> UpperFilter -> ConsoleOutput p := pipeline.Of(pipeline.DefaultConfig()) p.Exec() } // 运行结果 === RUN TestPipeline HELLO WORLD --- PASS: TestPipeline (0.00s) PASS原型模式(Prototype Pattern)简述原型模式主要解决对象复制的问题,它的核心就是clone()方法,返回Prototype对象的复制品。在程序设计过程中,往往会遇到有一些场景需要大量相同的对象,如果不使用原型模式,那么我们可能会这样进行对象的创建:新创建一个相同对象的实例,然后遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这种方法的缺点很明显,那就是使用者必须知道对象的实现细节,导致代码之间的耦合。另外,对象很有可能存在除了对象本身以外不可见的变量,这种情况下该方法就行不通了。对于这种情况,更好的方法就是使用原型模式,将复制逻辑委托给对象本身,这样,上述两个问题也都迎刃而解了。Go实现还是以建造者模式一节中的Message作为例子,现在设计一个Prototype抽象接口: package prototype ... // 原型复制抽象接口 type Prototype interface { clone() Prototype } ​ type Message struct { Header *Header Body *Body } ​ func (m *Message) clone() Prototype { msg := *m return &msg }测试代码如下: package test ... func TestPrototype(t *testing.T) { message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() // 复制一份消息 newMessage := message.Clone().(*msg.Message) if newMessage.Header.SrcAddr != message.Header.SrcAddr { t.Errorf("Clone Message failed.") } if newMessage.Body.Items[0] != message.Body.Items[0] { t.Errorf("Clone Message failed.") } } // 运行结果 === RUN TestPrototype --- PASS: TestPrototype (0.00s) PASS总结本文主要介绍了GoF的23种设计模式中的5种创建型模式,创建型模式的目的都是提供一个简单的接口,让对象的创建过程与使用者解耦。其中,单例模式主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点;建造者模式主要解决需要创建对象时需要传入多个参数,或者对初始化顺序有要求的场景;工厂方法模式通过提供一个工厂对象或者工厂方法,为使用者隐藏了对象创建的细节;抽象工厂模式是对工厂方法模式的优化,通过为工厂对象新增一个抽象层,让工厂对象遵循单一职责原则,也避免了霰弹式修改;原型模式则让对象复制更加简单。下一篇文章,将介绍23种设计模式中的7种结构型模式(Structural Pattern),及其Go语言的实现。
  • [分享交流] 如何解决“dos命令窗口可以执行java命令,无法执行javac命令”
    背景作为小白参与了“Java编程创造营”活动,老师在“Java编程基础课程”讲到java程序安装和环境配置。问题复现根据老师的流程成功安装了java8,配置完环境变量,在dos下成功检测到java版本:创建 HelloWorld.java/** * @author amufeng * @version v1.0 */ public class HelloWorld{ public static void main(String[] args){ int x=-1; x=x>>>5; System.out.print("Hi Java"+x); System.out.println("Hi Javavavava"); System.out.println("Javavavava"); } }执行编译HelloWorld.java,提示“‘javac’不是内部或外部命令,也不是可运行的程序或批处理文件”问题分析查看了一些CSDN博客,明确问题是环境变量配置不正确导致的。又查了一些资料,确认“java”命令属于JDK,而“javac”命令属于JRE,另一方面JRE属于JDK的一部分。问题可能是JRE的环境变量配置异常(或者缺少配置)。解决方法配置JDK和JRE环境变量:Path中添加JDK/JRE目录:重启电脑,再试javac命令,运行成功。编译、执行java程序:结束至此“dos命令窗口可以执行java命令,无法执行javac命令”问题解决,希望对有缘人有帮助。
  • [全栈开发者] (活动已结束)【Java编程创造营】第一阶段·最终考核
    经过一个月的学习,相信大家对Java有了基本的了解和认知我们迎来了第一阶段的最终考核请大家认真阅读考核要求,避免无法考核认证*报名活动才能参加最终考核  点此报名考核截止时间6月30日(超出考核时间不计入成绩)请遵守考试秩序,考试之前请务必仔细阅读以下注意事项:1)正式考试前请确定邮箱已绑定,如未绑定的,请登录华为云官网,按照出现的页面完成邮箱绑定。2)考试请使用Google Chrome(48版本以上)浏览器完成。3)考试次数为5次,请珍惜考试机会。每次考试会从题库中随机抽题。4)考试时间为40分钟,考试期间无法暂停,一旦超时,系统将自动提交试卷。交卷后即可查看到成绩。5)考试共25题,题型包括判断、单选和多选,总分100分,80分及以上即可通过考试,可直接获得阶段一结业电子证书6)考试过程中若有截屏或切屏行为,系统将发出警告,切屏次数达到5次,系统将自动提交试卷7)点击开始考试后,请耐心等待系统抽题,切勿重复刷新页面,否则导致题目抽取错误,成绩将会被作废。集齐三阶段电子证书可获得【Java编程创造营】结业实体证书考核流程Step1:进行Java开发技能考核点击进行Java开发技能测评(初级)Step2:考核后请将信息填写到有奖问卷中点击填写考核信息收集问卷阶段学习排行榜不仅有学习纪念证书,我们还为参与学习打卡的同学准备了实物奖品奖励通过获取积分,积分排行的方式获得奖励1)学习打卡赢积分学习第一阶段课程:Java编程基础课程:点此学习序号阶段任务详情活动积分奖励附加奖励提交入口1每章随堂测试任务完成方式:在随堂测试打卡帖中,回复对应章节的随堂    回复格式:华为云ID+截图(露出右上角华为云ID,且有效回复次数≥1)2分 /每有效打卡/每章节每周在本周内提交随堂测试的用户中,抽取1名幸运奖励华为云定制鼠标*1点击前往2每周学习笔记分享任务完成方式:在学习笔记征集帖中,回复本周课程内容的学习笔记    回复格式:华为云ID+笔记内容(字数≥200字)2分 /每有效打卡/每周每周在本周内提交学习笔记的用户中,抽取1名幸运奖励华为云定制鼠标*1点击前往3相关文章征集任务完成方式:本学习阶段任意时间内,在“华为云-博客”,发表与学习内容相关的任意原创博客内容,将文章链接回复到指定帖内    回复格式:华为云ID+博客文章链接(字数≥600字)5积分 /每篇有效心得/每阶段 最高10/每阶段每阶段选取1篇最佳博文,奖励机械键盘*1点击前往4问答官排位赛任务完成方式:用户可在相关活动帖中发布自己学习中的疑惑或实践问题,其他用户可参与回答。由专家评审,同一问题下确定一名最佳答案。回复格式:华为云ID+提问的问题/解答答案5积分/每有效问题(最高10分)2积分/每有答复(最高4分)最终会全部每阶段参与互动的用户中,抽取3位幸运奖奖励华为云定制鼠标*1点击前往5完成阶段沙箱实验完成规定的沙箱实验20分/ 沙箱实验/点击前往6完成相关微认证完成规定的微认证并获得微认证证书(官方认证)30分/认证/点击前往点击了解Java编程创造营专属产品福利2)参加限时活动赢积分【有奖征文】奔跑吧,JAVA!-- 26周年特别策划今年五月,编程语言—JAVA迎来了自己的第26个生日。华为云社区特举办 Java主题征文活动,来庆祝这个属于技术的节日参与征文活动将会有机会获得精美礼品,优秀的文章还将被汇编成册,提供实践参考。报名【Java编程创造营】并参与此有奖征文活动将会获得额外积分加分发表文章+5分/篇(上限20分),TOP20文章 +10分/篇(上限20分)参与本次征文活动后,将参与活动的文章链接私信给小助手视为参与成功,活动时间与征文时间同步。征文活动参与详情请见:https://bbs.huaweicloud.com/blogs/265241-----------------------------------------------------------------------------------------注意事项:○获奖名单将在活动结束后3个工作日内公示。如如有异议请于【华为云课程小助手】联系○所有活动奖品将在活动公示后15个工作日内完成发放;○活动奖品颜色、型号随机,且部分奖品数量有限发完即止,,若有缺货或其他情况将替换为同价值的其他奖品;○本次活动回帖内容需满足华为云论坛发帖规范:https://bbs.huaweicloud.com/forum/thread-23077-1-1.html想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [二次开发] HD 6.5.1.7 Elasticsearch  用java代码查询出来的结果是科学计数法,有没有什么方法让直接返回数值型,不
    【操作步骤&问题现象】head插件查询出来的结果不以科学记数法表示,java代码查询的聚合结果是科学记数法索引中原字段是double目前只想到一个一个通过DecimalFormat解析结果做格式化处理,但这样太麻烦。ES中是否有直接指定格式返回的方式?还有比较奇怪的是数值只有 266144704 也不算大,但还以科学记数法显示了。
  • [全栈开发者] 【Java编程创造营】Java相关沙箱实验打卡贴
    为了巩固大家的学习成果,紧跟学习进度请将需要完成的相关沙箱实验按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~征集时间课程全阶段微认证考核要求提交操作结果截图 阶段考核+20分通过特定场景化学习和实际操作,可以实现对学习内容的理解和认知Java编程创造营使用以下场景参与认证考核通过鲲鹏开发套件实现Java代码迁移沙箱链接:https://lab.huaweicloud.com/testdetail_439?ticket=ST-929855-D7amfkTdlHCyKpohgDOjudkO-sso参与认证的同学请将【已完成100%&实验报告截图+华为云ID】回复到本帖当中 才能算作最终考核成功,考核分数才能统计活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成微认证任务,可获得的积分上限为20分;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [技术干货] Java 多线程编程
    新建状态:使用 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.
  • [技术干货] 浅谈java中的对象、类、与方法的重载
    对象:一切皆为对象。对象包括两部分内容:属性(名词形容词),行为(动词)。对象和对象之间是有关系的: 派生,关联,依赖。类:对同一类别的众多对象的一种抽象。类,还是用来生成对象的一种模板,对象是类的一种具体化的表现。面向对象的三大特性:封装,继承,多态。1234class 类名{访问修饰符 成员变量的定义;访问修饰符 成员函数(方法)的定义; }访问修改符:默认不写,private,public。private,私有。只能被当前class 类名{}中的代码访问到。出了这个类的{},访问不到了。public,公有。能被当前class 类名{}内访问。也能被其它位置的对象访问。默认不写,在当前class 类名{}中可以访问。当前包中的其它类中也可以访问。出了当前包,就访问不到了get方法和set方法1.get成员变量名()方法:当成员变量是private的时候,用来获取成员变量。1.set成员变量名()方法:当成员变量是private的时候,用来直接改变成员变量。类的实例化:用类来生成对象。类名 变量名 = new 类名();类名 变量名 = new 构造函数;构造函数:必须与类名相同。不能有返回类型。自动被执行。对象new出的来时间自动被执行。一般都是public。先执行成员变量的初始化,在执行成员函数。不写一般也会被系统默认创建。1234567891011121314151617public class gou { //成员变量 private String mingZi = "大黄" ;  private String zhuRen; //成员函数(方法) public String getMingZi() {  return mingZi; } public void setMingZi(String mingZi) { if(mingZi != zhuRen){  this.mingZi = mingZi; }  } public void jiaoHuan(){ System.out.println(mingZi+":旺!旺!旺!"); }}123456public class home { public static void main(String[] args) { gou a = new gou(); a.setMingZi("小黄");     //用set方法修改名字。 System.out.println("小狗的名字是:"+a.getMingZi()); //用get方法获取名字。 a.jiaoHuan();方法的重载:函数名相同,参数不同(数量,类型)。这两个函数就形成了重载。重载与函数返回类型号没有关系,重载只跟函数名和参数有关系。重载了的函数,会自动跟据调用的实参来决定调用哪个函数。除了构造函数可以重载,普通成员函数也可以重载
  • [技术干货] Java8接口的默认方法
    Java8接口的默认方法什么是默认方法,为什么要有默认方法?简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了使接口没有引入与现有的实现不兼容发展。如以下所示,12345public interface Animal {  default void eat() {    System.out.println("animal eat default method");  }}声明了一个接口,里面只有一个默认方法。然后写一个具体的类实现这个接口,12345678910public class Dog implements Animal {  public void sayHi() {    System.out.println("dog");  }    public static void main(String args[]) {    Dog dog = new Dog();    dog.eat();  }}再具体的类里面不是必须重写默认方法,但必须要实现抽象方法。默认方法的多重继承如下所示代码,1234567891011121314151617181920212223242526272829303132333435public interface A {    void doSomething();    default void hello() {    System.out.println("hello world from interface A");  }    default void foo() {    System.out.println("foo from interface A");  }}  interface B extends A {  default void hello() {    System.out.println("hello world from interface B");    A.super.hello();    this.foo();    A.super.foo();  }}  class C implements B, A {    @Override  public void doSomething() {    System.out.println("c object need do something");  }    public static void main(String args[]) {    A obj = new C();    obj.hello();//调用B的方法    obj.doSomething();  }}123456789hello world from interface B hello world from interface A foo from interface A foo from interface A c object need do somethingobj.hello()调用的是B接口中的默认方法。同时在B接口中的默认方法有调用了父接口中的默认方法。我们再来看一个例子,思考一下在多重继承中,如果出现了同名的默认方法,如下所示,123456789101112131415161718192021222324252627public interface D {  default void hello() {    System.out.println("hello world from D");  }}  interface E {  default void hello() {    System.out.println("hello world from E");  }}  class F implements D, E {    @Override  public void hello() {    System.out.println("hello world F class");    D.super.hello();    E.super.hello();  }    public static void main(String args[]) {    F f = new F();    f.hello();  }  }我们需要制定调用哪个接口的默认方法如下,12D.super.hello();E.super.hello();另一个java8的接口默认方法实例:java8新增了接口的默认方法, 也就是说在接口中也可以有实现了, 这个实现方法是默认的实现,你也可以在接口的实现类里对此默认方法进行重写。如下实例:12345678910111213141516171819202122232425262728293031323334353637383940public class AppInterfaceDefaultMethod {    public static interface DefaultMethodDemo {    //定义默认方法, 默认方法前面加default关键字, 后面跟方法声明和方法体    default void demo(String input) {      System.out.println(input);    }      void doSomething();  }    public static class DemoClass implements DefaultMethodDemo {    @Override    public void doSomething() {      System.out.println("do something");    }  }    public static class DemoClassOverrideDemo implements DefaultMethodDemo {    //重写了默认方法    @Override    public void demo(String input) {      System.out.println("demo " + input + " by override method");    }      @Override    public void doSomething() {        System.out.println("do something");    }  }    public static void main(String[] args) {    DefaultMethodDemo demo = new DemoClass();    demo.demo("abc");      DefaultMethodDemo demoOverride = new DemoClassOverrideDemo();    demoOverride.demo("abc");  }}以上就是关于Java8接口的默认方法详细介绍,希望对大家的学习有所帮助。打印结果:
  • [技术干货] java中中英文实现首字母排序
    在Java中对于字符串数组的排序,我们可以使用Arrays.sort(String[])方法很便捷的进行排序。例如:1234567String[] arrays = new String[] { "gyu", "sdf", "zf", "大同", "收到", "地方", "三等分", "的人", "反对高铁", "泛代数", "上的投入", "和国家" };/*设置语言环境*/Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);Arrays.sort(arrays, com);for (String item:arrays) { System.out.print(item+" ");}输出的结果为:“gyu  sdf  zf  大同  的人  地方  反对高铁  泛代数  和国家  三等分  上的投入  收到”;在Java中排列的顺序是按照数字->英文->汉字进行排序的,这种排序方式可以满足部分要求,但很多情况下我们并不希望这样排列!例如Anroid中的通讯录,音乐播放列表等等,这些情形下我们希望英文首字母和中文拼音首字母一样的排列在一起以方便查询。由于这类排序算法很复杂,例如首字母相同的字符串还要接着比较第二个、第三个...。但是如果我们将jdk自带的排序加以应用就不会显得那么复杂了;我的想法是这样的:既然Java中排序是按数字->英文->汉字来进行排序的,那我们就把每个汉字打头的字符串前面加上一个该字符串第一个字符的拼音的首字母和一个区分符“&”,再使用jdk提供的排序函数进行排序,这时我们得到的就是我们想要的排序的数组了。然后再遍历数组,将包含&符号的字符串去掉&和第一个英文字母便完成了整个排序了,具体实现代码如下(获取汉字拼音需要引用该jar:pinyin4j-2.5.0.jar):12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849/*** 将字符串数字按首字母先后进行排序 * * Java原生排序为 数字->英文->中文 * 为了将英文和中文首字母相同的排列到一起 * 先将字符串首字符为汉字的改为该汉字的首字母加上该字符串 * 为了以示区分中间再加一个分割符& * 然后使用Java原生排序算法 * 再将包含&字符的字符串中的&和首字母去除从而达到排序目的 * */public static void main(String[] args) {  String[] arrays = new String[] { "gyu", "sdf", "zf", "大同", "收到", "地方", "三等分", "的人", "反对高铁", "泛代数", "上的投入", "和国家" }; for (int i = 0; i < arrays.length; i++) {  String str = arrays[i];  if (str.length() == 0)  return;  String alphabet = str.substring(0, 1);  /*判断首字符是否为中文,如果是中文便将首字符拼音的首字母和&符号加在字符串前面*/  if (alphabet.matches("[\\u4e00-\\u9fa5]+")) {  str = getAlphabet(str) + "&" + str;  arrays[i] = str;  } } /*设置排序语言环境*/ Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA); Arrays.sort(arrays, com); /*遍历数组,去除标识符&及首字母*/ for (int i=0;i<arrays.length;i++) {  String str=arrays[i];  if(str.contains("&")&&str.indexOf("&")==1){  arrays[i]=str.split("&")[1];  }  System.out.println(arrays[i]); } } public static String getAlphabet(String str) { HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); // 输出拼音全部小写 defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE); // 不带声调 defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); String pinyin = null; try {  pinyin = (String) PinyinHelper.toHanyuPinyinStringArray(str.charAt(0), defaultFormat)[0]; } catch (BadHanyuPinyinOutputFormatCombination e) {  e.printStackTrace(); } return pinyin.substring(0, 1);}这时输出结果为:“大同 的人 地方 反对高铁 泛代数 gyu 和国家 三等分 上的投入 收到 sdf zf”,大家也可以自己尝试自己写排序算法去实现,锻炼一下思维也不无坏处,呵呵。
总条数:2301 到第 页
上滑加载中