原文出自:http://blog.csdn.net/anxpp/article/details/51914119,转载请注明出处,谢谢!
1、前言
OK,之前写了一篇文章:“23种设计模式介绍以及在Java中的应用”详细介绍了如何将设计模式应用到Java编程中,而本文旨在介绍如何利用他们优化我们的程序,使其性能更佳。
设计模式的详细介绍请参照上面链接中的文章,不是本文的重点。
而Java程序的性能优化,不一定就仅仅是以提高系统性能为目的的,还可能是以用户体验、系统可维护性等为目的。
2、概述
我们知道,设计模式能够大大的优化我们的代码,是针对某一类问题的最优解决方案,是从许多优秀的软件系统中总结出的。
本文重点是Java程序性能优化中的设计优化,介绍的设计模式都是与性能相关的。
出了设计模式,文章也会介绍一些相关的设计组件和设计方法。
3、合理使用设计模式
该节中涉及的设计模式,都在文首链接的文章中详细的介绍了,如果不清楚他们的实现,请先阅读它们。
3.1、单例模式
单例模式最容易理解,也最常见,是一种对象创建模式。
下面是一种简单但正确的实现方式:
- public enum EasySingleton{
- INSTANCE;
- }
上面的方式实现代理,利用的是Java自有的机制,线程是安全的,也不担心被使用反射强行调用构造方式产生多个实例。
在Java语言中,单例模式能明显带来的好处:
1、对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象来说,尤为明显。
2、由于没有new更多的对象,内存使用频率也降低,从而减轻GC(垃圾回收器)的压力。
不过单例模式切不可滥用,不然很容易造成内存泄露,可参考:JAVA 内存泄露详解
3.2、代理模式
代理模式也很常用,可以屏蔽用户对真实对象的访问。
而且代理模式还能简单的实现AOP(面向切面编程)。
但是涉及到性能的,主要是体现在使用代理模式实现延迟加载。延迟加载的核心思想是:如果当前没有使用这个对象(或组件),就不需要真正的创建它。
3.2.1、简单示例
下面还是要看一个简单的示例:
- package com.anxpp.hello911.proxy;
- //使用
- public class SimpleProxy {
- public static void main(String[] args) {
- IBase base = new BaseProxy(); //使用代理
- base.doSomeThing(); //使用时懒加载
- }
- }
- interface IBase{
- String doSomeThing();
- }
- //类本身,通常是重量级的对象
- class Base implements IBase{
- public Base() {
- try {
- //构造这个对象可能是非常耗时的操作
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- @Override
- public String doSomeThing() {
- return "result";
- }
- }
- //代理类
- class BaseProxy implements IBase{
- private IBase base = null;
- @Override
- public String doSomeThing() {
- if(base==null)
- base = new Base();
- return base.doSomeThing();
- }
- }
当我们要使用的对象是一个相对重量级的时候,我们可以使用上面的代理模式,为这个对象作懒加载。
为什么会优化性能呢?这是对用户而言,因为对象是懒加载的,比如,系统启动速度会有效(基于这个对象的构造复杂度)提升,因此会很好的改善用户的体验。再者,有些组件,可能再程序的生命周期并不一定会被调用,而使用懒加载,无疑就避免了这些资源的浪费。
3.2.2、动态代理
动态代理是在运行时,动态的生成代理类。
如果类本身实现的接口中包含了较多的方法,那么,为每个接口都实现一个代理类,是比较繁琐的。 如果接口发生改变,真是对象和代理对象都要修改。但是若使用代理模式生成方法,将大大优化上面出现的问题。
3.3、享元模式
享元模式的主要目的就是提高系统性能,就是真正意思上的性能优化了。
享元模式的最佳实践之一,就是Java中的String类了,相信大家也是非常清楚的。
其核心原理:如果系统中存在多个相同的对象,那么只需要共享一分对象的拷贝。
对性能的提升主要体现在两点:
1、节约重复创建对象的开销。
2、减小系统内存的开销。
3.4、装饰者模式
装饰者模式巧妙的设计结构:可以动态的为对象添加功能。
根据“合成/聚合”复用原则,代码复用应该尽可能使用委托(即组合),而不是继承,因为继承是一种紧耦合的,父类的任何改动都会影响其子类,不利于系统维护。但是委托使松耦合的,只要接口不变,委托类的改动并不会影响其上层对象。
装饰者模式可以有效的分离性能组件和功能组件,从而提升模块的可维护性并增加模块的复用性。
具体实现请参考文首的文章链接中的相关内容。
3.5、观察者模式
观察者模式也是一种比较常用的设计模式。
系统中的一个对象的行为依赖另一个对象时,观察者模式就相当有用。
如果在常规方法中,需要实现类似功能的话,需要开启多个线程监控所依赖的对象的状态(多线程实际上是会加重系统的负担的),但是入股欧使用观察者模式,实现这样的功能可以在单线程中完成。
观察者模式可以用于时间的监听、通知发布等场合,可以确保将这个变化通知给观察者。
JDK中也实现了一套观察者模式,详见文首链接的文章。
3.6、Value Object模式
Value Object是一种什么样的模式呢?其实就是将一组操作原子化。
比如,如果我们要从数据库取得一组特定的结果,他们可能需要经过多次查询或者还需要计算,那么如果仅仅是在Java代码中经过多次查询并组织成我们的目标结果,可能并不是一个好的设计。相反,我们可以将这些属性,封装到一个对象中,然后使用存储过程(Oracle也直接导入Java代码来代替存储过程,如果数据需要经过大量的计算处理,这将比存储过程效率高很多)将这些操作原子化,大大减少数据库访问次数,就能达到系统优化的目的。
而这种模式的应用不仅仅上上面这点,比如我们前端访问服务器取得数据,需要经过多次取值再组合显示的话,我们完全可以将这些属性封装到一个对象中,同意处理后返回,会减少服务器访问次数(不过如果需要懒加载,就需要分步获取数据)。
3.7、业务代理模式
业务代理模式可能概念上有点近似Value Object模式,不过它封装的不是一系列操作的属性,而是将部分业务流程组合到一起。
我们也可以将其与模板方法做对比,他们都是将一系列操作组合到一起。
业务代理模式将一些业务流程封装在前台系统,为系统性能优化提供了基础平台。
4、组件和方法
4.1、缓冲(Buffer)
缓冲区是一块专门用于化解程序中各个模块(或者层次)间的性能差异的,可以提高系统性能。
使用的地方也很多,比如我们要将接收到的数据保存到硬盘中,从网络I/O中读取的数据是不稳定的,可能会很快,但是写入数据到硬盘的速度基本是固定的,两者是有差异的。我们可以通过在内存中开辟一块用来缓存数据,然后按一定的机制统一写到硬盘中,以协调两者的速度不匹配问题。这时候,如果从网络来的数据比较集中,也不用等数据都写入带硬盘后再继续接收剩余的数据,相反,如果网络I/O的数据很少,也可以先放到缓冲中,等到数据量达到一定程度后再统一写入硬盘。
缓冲可以协调上层组件和下层组件的性能差异。当上层组件性能优于下层组件时,可以有效减少上层组件对下层组件的等待时间。
由于I/O操作很容易成为性能瓶颈,所以尽可能在I/O读写中加入缓冲组件,以提高性能。
4.2、缓存(Cache)
缓存也是一块内存中专门开启的空间,主要是缓存数据处理结果,并提供给下一次访问。
比如我们查询数据库的结果,如果没有修改数据库中的数据,那么每次查询的结果应该是一样的,我们没必要就每次要去查询一次,直接将结果缓存到内存中,下次需要的时候直接从内存中取即可。
缓存的使用场合非常多,所以现在也有很多流行的支持缓存的NoSQL数据库,比如Redis。
目前也有很多缓存使用相关的框架,不仅可以缓存数据到本地,还可以缓存到远程分布式缓存服务器中。
缓存可以保存一下来之不易的数据或计算结果。当需要再次使用这些数据时,可以从缓存中低成本的获取。
4.3、池
对象池化也是一个非常常用的系统优化技术。
核心思想是:如果一个类被频繁请求使用,那么不必每次都生成一个示例,可以将这个类的一些实例保存在一个“池”中,待需要使用时,直接从池中获取。
实现上,它可以是数组、链表等任何集合。
可能使用最多的就是线程池,线程池中保存的就是可以被重用的线程对象,当任务呗提交到线程池时,系统并不一定需要新建线程,而是从池中获取一个可用的线程来执行这个任务。
在程序中使用数据库连接池和线程池,可以有效的改善系统咋高并发下的性能。而目前可能对数据库操作都常使用一些ORM框架,它们都很好的封装了数据库连接池,可能碰到更多的,还是线程池。当然,其他类似的情况,我们也可以考虑自己实现一个池。
4.4、并行而不是串行
也就是多线程技术,它可以将CPU的潜能发挥到最大化。
并发相关技术请参考博客总并发相关的文章。
4.5、负载均衡
对于一些大型应用,单台服务器是无法满足需求的,可能就需要部署多台作集群,然后使用负载均衡技术将工作尽可能的平均分配到各个服务器上。
比如一个Web服务器,如果较高的并发已经导致系统频繁的无响应等,通常就会配置多台服务器集群,然后使用Nginx等应用作负载均衡,将请求分配到不同的服务器上。而Session的管理,可能就会使用诸如Redis等缓存数据库,统一管理。
4.6、时间与空间之间的权衡
对于不同的业务类型,可能对时间(CPU)和空间(存储)的需求也各不相同。
性能的优化在于掌握各部分组件的性能平衡点。如果系统CPU资源有空闲,但是内存使用紧张,便可以考虑使用时间换空间的策略,大道整体性能的改进。相反,如果CPU资源紧张,就可以考虑用空间换时间。
在很多算法(比如常见的排序算法),他们对的时间复杂度和空间复杂度是各不相同的,在使用时没应考虑时间和空间的权衡。
5、总结
本文主要是探讨在设计上如何优化我们的程序,而优化并不一定就完全是提高系统的性能,还可能是用户体验、可维护性等。