编写java程序151条建议读书笔记(19)

来源:互联网 发布:mysql function 编辑:程序博客网 时间:2024/06/04 19:43

建议130:使用CountDownLatch协调子线程

百米赛跑。多名参赛者听到发令枪响开始跑步,到达终点统计成绩,这里要考虑两点:一是发令枪响所有跑步者(线程)接到信号,此处涉及到如何通知跑步者(子线程)的问题,二是如何获知所有跑步者完成比赛也就是主线程知道所有子线程全部完成,有许多种实现方式,下面使用CountDownLatch工具类实现:

static class Runner implements Callable<Integer>{//开始信号private  CountDownLatch begin;//结束信号private  CountDownLatch end;public Runner(CountDownLatch _begin,CountDownLatch _end){begin=_begin;end=_end;}@Overridepublic Integer call()throws Exception{//跑步成绩int score=new Random().nextInt(25);//等待发令枪响begin.wait();//跑步中TimeUnit.MILLISECONDS.sleep(score);//跑步结束end.countDown();return score;}public static void main(String[] args) throws Exception {//参赛人数int num=10;//发令枪响一次CountDownLatch begin=new CountDownLatch(1);//参与跑步者有多个CountDownLatch end=new CountDownLatch(num);//每个跑步者一个跑道ExecutableElement es=(ExecutableElement) Executors.newFixedThreadPool(num);//记录成绩List<Future<Integer>>futures=new ArrayList<Future<Integer>>();//跑步者就为所有线程等待状态for(int i=0;i<num;i++){futures.add(((ExecutorService) es).submit(new Runner(begin, end)));}//发令枪响开始跑步begin.countDown();//等待所有跑步者完成end.wait();int count=0;//统计总分for(Future<Integer>f:futures){count+=f.get();}System.out.println("平均成绩"+count/num);}}

CountDownLatch协调子线程步骤:一个开始计数器,多个结束计数器:1)每一个子线程开始运行,执行代码到begin.await后线程阻塞,等待begin的计数变为0;2)主线程调用begin的countDown方法,使begin的计数器为0;3)每个线程继续运行;4)主线程继续运行下一条语句,end的计数器不为0,主线程等待;5)每个线程运行结束时把end的计数器减1,标志着本线程运行完毕;6)多个线程全部结束,end计数器为0;7)主线程继续执行,打印出结果。类似:领导安排了一个大任务给我,我一个人不可能完成,于是我把该任务分解给10个人做,在10个人全部完成后,我把这10个结果组合起来返回给领导--这就是CountDownLatch的作用)。

建议131:CyclicBarrier让多线程齐步走

解决独立线程在没有线程通信的情况下,在线程汇聚同一源点的问题,java提供了(关卡CyclicBarrier,也翻译成栅栏)

class worker implements Runnable{private CyclicBarrier cb;public worker(CyclicBarrier _cb){cb=_cb;}public void run(){try {Thread.sleep(new Random().nextInt(1000));System.out.println(Thread.currentThread().getName()+"到达会和点");cb.wait();} catch (InterruptedException e1) {// TODO Auto-generated catch block}}public static void main(String[] args) throws Exception{CyclicBarrier cb=new CyclicBarrier(2,new Runnable() {@Overridepublic void run() {System.out.println("隧道已打通");}});//工人1挖隧道new Thread(new worker(cb),"工人1").start();//工人1挖隧道new Thread(new worker(cb),"工人2").start();}}
这里定义了两个线程和汇聚的关卡,逻辑就是:1)2个线程同时运行实现不同任务执行时间不同。2)1先到达汇合点变为等待状态。3)2到达汇合点满足预先的关卡条件继续执行,此时还有额外的执行动作,执行关卡任务(就是run方法)和唤醒1线程。4)1线程继续执行。CyclicBarrier关卡可以让所有线程全部处于等待状态(阻塞),然后在满足条件的情况下继续执行,这就好比是一条起跑线,不管是如何到达起跑线的,只要到达这条起跑线就必须等待其他人员,待人员到齐后再各奔东西,CyclicBarrier关注的是汇合点的信息,而不在乎之前或者之后做何处理。CyclicBarrier可以用在系统的性能测试中,测试并发性。

建议132:提升Java性能的基本方法

如何让Java程序跑的更快、效率更高、吞吐量更大:

1、不要在循环条件中计算,每循环一次就会计算一次,会降低系统效率;

2、尽可能把变量、方法声明为final static类型,加上final static修饰后,在类加载后就会生成,每次方法调用则不再重新生成对象了;

3、缩小变量的作用范围,目的是加快GC的回收;

4、频繁字符串操作使用StringBuilder或StringBuffer;

5、使用非线性检索,使用binarySearch查找会比indexOf查找元素快很多,但是使用binarySearch查找时记得先排序

6、覆写Exception的fillInStackTrace方法,fillInStackTrace方法是用来记录异常时的栈信息的,这是非常耗时的动作,如果不需要关注栈信息,则可以覆盖,以提升性能;

7、不建立冗余对象。

程序的运行需要三种资源CPU、内存、I/O,提升CPU的处理速度可以加快代码的执行速度,变现在反回时间缩短了效率提高了,内存是java程序必须考虑的问题,在32位机器上,一个JVM最多能使用2GB的内存,而程序占用内存越大寻址效率也越低,I/O是程序展示和存储数据的主要通道,如果他很缓慢会影响正常的展示效果。随着java升级很多看似正常的优化策略都逐渐过时,优化方法还要自我验证。

建议133:若非必要,不要克隆对象

通过clone方法生成一个对象时,就会不再执行构造函数了,只是在内存中进行数据块的拷贝,看上去似乎应该比new方法的性能好很多,但事实上,JVM对new做了大量的系能优化,(多数都是new出来的所以认识到这点)一般情况下new生成的对象比clone生成的性能方面要好很多。而clone方式只是一个冷僻的生成对象的方式,并不是主流,它主要用于构造函数比较复杂,对象属性比较多,通过new关键字创建一个对象比较耗时间的时候。

建议134:推荐使用“望闻问切”的方式诊断性能

1、望:观察性能问题症状:性能问题从表象上分两类,一是不可(或很难)重现的偶发性问题,比如线程阻塞,多线程访问共享资源不会死锁但会被阻塞,此时就很难去重现。二是可重现性能问题。

2、闻:性能优化上的闻是关注项目被动产生的信息,包括项目组的技术能力(主要取决于技术经理的技术能力)、文化氛围。群体的习惯和习性以及他们的专注和擅长的领域。注意的是闻并不是主动地去了解,而是由技术(人或应用)自行发挥出的“味道”需要我们敏锐的抓住,这对性能分析有很大的帮助。

3、问:就是技术人员和业务人员(使用者)一起探讨,了解问题的历史状况。

4、切:给出定论,java系统性能也是类似,接触真是的系统数据,需要看设计、代码、日志。系统环境、分析的结论。这里注意所有的一手资料(如报告、非系统信息)都不是百分百可靠,二是测试环境毕竟是测试环境它是证明假设的辅助工具并不能证明方法或策略的正确性。

建议135:必须定义性能衡量标准

性能目标的重要性:1)性能衡量标准时技术与业务之间的契约:如“非常快”不具有衡量性,如2秒对于开发者和业务者的体会是不同的。2)性能衡量标志是技术优化的目标,性能优化无底线但副作用也越明显,如可读性差,可扩展性降低等。一个好的性能标准应包括以下KPI(key Performance Indicators):1)核心业务的响应时间。(如新闻网站的核心业务是新闻浏览,衡量标准就是打开一个新闻的时间)。2)重要业务的响应时间,重要业务是在系统中占据前沿地位的业务,但不会涉及业务数据的功能(邮件系统的登录)。性能的衡量标准要在一定的环境下。

建议136:枪打出头鸟--解决首要系统性能问题

系统出现性能问题很少是一个功能有问题。一般是一批出现,统计重要导致缓慢的按优先级排除前三解决。解决性能优化要“单线程”小步前进,避免关注点过多而导致精力分散(解决性能问题时,不要把所有的问题都摆在眼前,这只会“扰乱”你的思维,集中精力,找到那个“出头鸟”,解决它,在大部分情况下,一批性能问题都会迎刃而解。

建议137:调整JVM参数以提升性能

如果程序优化达到极致就来到了JVM优化,四个常用的JVM优化手段:
1、调整堆内存大小,JVM两种内存:栈内存(Stack)和堆内存(Heap),栈内存的特点是空间小,速度快,用来存放对象的引用及程序中的基本类型;而堆内存的特点是空间比较大,速度慢,一般对象都会在这里生成、使用和消亡。栈空间由线程开辟,线程结束,栈空间由JVM回收,它的大小一般不会对性能有太大影响,但是它会影响系统的稳定性,超过栈内存的容量时,会抛StackOverflowError错误。可以通过“java -Xss <size>”设置栈内存大小来解决。堆内存的调整不能太随意,调整得太小,会导致Full GC频繁执行,轻则导致系统性能急速下降,重则导致系统根本无法使用;调整得太大,一则浪费资源(若设置了最小堆内存则可以避免此问题),二则是产生系统不稳定的情况,设置方法“java -Xmx1536 -Xms1024m”,可以通过将-Xmx和-Xms参数值设置为相同的来固定堆内存大小;

2、调整堆内存中各分区的比例,JVM的堆内存包括三部分:新生区(Young Generation Space)、养老区(Tenure Generation Space)、永久存储区(Permanent Space 方法区),其中新生成的对象都在新生区,又分为伊甸区(Eden Space)、幸存0区(Survivor 0 Space)和幸存1区(Survivor 1 Space),当在程序中使用了new关键字时,首先在Eden区生成该对象,如果Eden区满了,则触发minor GC,然后把剩余的对象移到Survivor区(0区或者1区),如果Survivor取也满了,则minor GC会再回收一次,然后再把剩余的对象移到养老区,如果养老区也满了,则会触发Full GC(非常危险的动作,JVM会停止所有的执行,所有系统资源都会让位给垃圾回收器),会对所有的对象过滤一遍,检查是否有可以回收的对象,如果还是没有的话,就抛出OutOfMemoryError错误。一般情况下新生区与养老区的比例为1:3左右,设置命令:“java -XX:NewSize=32m -XX:MaxNewSize=640m -XX:MaxPermSize=1280m -XX:NewRatio=5”,该配置指定新生代初始化为32MB(也就是新生区最小内存为32M),最大不超过640MB,养老区最大不超过1280MB,新生区和养老区的比例为1:5.一般情况下Eden Space : Survivor 0 Space : Survivor 1 Space == 8 : 1 : 1)。

3、java程序性能最大的障碍就是垃圾回收不知道它何时发生,也不知道执行时间,可以想办法改变对系统影响,比如启动并行、规定并行回收线程数量等,变更GC的垃圾回收策略,设置命令“java -XX:+UseParallelGC -XX:ParallelGCThreads=20”,这里启用了并行垃圾收集机制,并且定义了20个收集线程(默认的收集线程等于CPU的数量),这对多CPU的系统时非常有帮助的,可以大大减少垃圾回收对系统的影响,提高系统性能;

4、更换JVM,如果所有的JVM优化都不见效,那就只有更换JVM了,比较流行的三个JVM产品:Java HotSpot VM(稳定性可靠性不错)、Oracle JRockit JVM(效率著称)、IBM JVM(稳定)。

建议138:性能是个大“咕咚”

不要被未知吓到跟风,1、没有慢的系统,只有不满足义务的系统;2、没有慢的系统,只有架构不良的系统;3、没有慢的系统,只有懒惰的技术人员;4、没有慢的系统,只有不愿意投入的系统