从设计模式的角度看Java程序优化
一、前言
Java程序优化有很多种渠道,比如jvm优化、数据库优化等等,但都是亡羊补牢的措施,如果能在设计程序架构时利用设计模式就把程序的短板解决,就能使程序更加健壮切容易维护迭代
二、常用的设计模式
1、单例模式
单例模式可以确保一个类只产生一个实例,对于系统中频繁使用的对象建议使用单例模式设计,可以节省创建对象耗费的时间以及减少GC清理的压力,代码如下
public class A { // 实例化一个私有的静态对象 private static A a = new A(); // 构造方法私有,防止其他代码对其实例化 private A() { } // 通过getInstance获取使用该对象 public static A getInstance() { return a; } }
因为对象实例是静态对象,所以JVM在加载这个类时就会把对象创建出来,这样无法做到延时加载(就是用到的时候再加载),所以还有一些写法会将对象设置为null,当有人调用getInstance方法时,先判断对象是否为空,为空再实例化对象,达到延时加载的目的,代码如下
public class A { // 声明对象时赋值为null,加载类时,不会生成对象 private static A a = null; private A() { } // 加入synchronized关键字,将这个办法锁起来,当多线程调用时竞争锁,达到线程安全 public static synchronized A getInstance() { if (a == null) { a = new A(); } return a; } }
以上代码虽然解决了线程安全的问题,但是加入同步锁,会使代码效率降低为代价,所以还需要再改进一下,完美的做法如下
public class A { private A() { } // 声明一个静态内部类 private static class AHolder { private static A a = new A(); } public static A getInstance() { // 直接返回内部类的对象 return AHolder.a; } }
以上代码声明了一个静态内部类,当调用A类时,内部类是不会被初始化的,当调用getInstance方法时,内部类才会被调用,内部类中的对象就会随之实例化,这样使用内部类巧妙的达成了延时加载的目的,同时也不用加锁,性能不会降低
2、代理模式
使用代理模式可以使用一个代理类来间接调用其他类的功能,达到屏蔽主类的效果,在程序优化的角度上,他可以做到:当系统启动时,会做许多初始化的操作,导致系统启动时非常缓慢,可以使用代理类来处理初始化的操作,当系统启动时,加载代理类,而不用做真正的初始化操作,当这个组件真正被用到时,代理类才会真正去调用初始化的操作,这样达到了系统压力的分摊,而加快了系统的启动速度。当然代理类除了应对性能问题,还有很多其他的用途。代码如下
public interface IOperation { // 业务逻辑接口 void doIt(); }
public class TrueOperation implements IOperation { @Override public void doIt() { // 真正的业务逻辑 System.out.println("do it."); } }
public class ProxyOperation implements IOperation { // 聚合真正的实现类 private TrueOperation trueOperation = null; @Override public void doIt() { if (trueOperation == null) { trueOperation = new TrueOperation(); } // 代理执行真正的方法 trueOperation.doIt(); } }
以上代码可以看出,其实代理类就是将真正的业务类聚合到自己,然后代执行,只有当业务方法真正被调用时,业务类才会被初始化,也是延时加载的思想,以上是静态代理模式的写法,存在的问题就是,当业务多了以后,同时还要维护代理类,这样就增加了开发维护的难度,所以进一步优化就是使用动态代理模式,如下代码使用JDK自带的动态代理模式
// 业务逻辑接口 public interface IOperation { void operationA(); void operationB(); }
// 业务逻辑的实现 public class OperationImpl implements IOperation { @Override public void operationA() { System.out.println("do A..."); } @Override public void operationB() { System.out.println("do B..."); } }
// 动态代理实现类 public class OperationHandler implements InvocationHandler { // 需要代理的业务逻辑 private OperationImpl operation = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 延时加载 if (operation == null) { operation = new OperationImpl(); } // 执行一些自定义的逻辑 System.out.println("before work..."); // 代理执行业务逻辑 method.invoke(operation, args); // 执行一些自定义的逻辑 System.out.println("after work..."); return null; } }
public class Main { public static void main(String[] args) { // 生成代理对象 IOperation operation = (IOperation)Proxy .newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IOperation.class}, new OperationHandler()); // 代理执行业务 operation.operationA(); operation.operationB(); } }
如果需要更好性能的动态代理模式,可以使用第三方库,比如CGLIB、Javassist等
3、享元模式
当系统中存在多个相同的对象,可以优化让大家都是用同一个对象,工厂类中拿着一个对象实例,当需要使用到这个对象时,通过工厂类获取对象实例,不用每次使用都创建一遍对象,从而减少创建开销、GC压力和对象的内存占用。这听起来有点像单例模式和对象池的合体,这与对象池的区别就是:对象池中的每个对象都是等价的,而享元模式中的工厂的对象,则各有各自的用途,如下代码
// 业务逻辑接口 public interface IOperation { void doIt(); }
// 业务逻辑的实现 public class OperationImplA implements IOperation { private String id = null; public OperationImplA(String id) { this.id = id; } @Override public void doIt() { System.out.println("do it A..."); } }
// 业务逻辑的实现 public class OperationImplB implements IOperation { private String id = null; public OperationImplB(String id) { this.id = id; } @Override public void doIt() { System.out.println("do it B..."); } }
// 享元工厂 public class OperationFactory { // 相同ID公用同一个对象 private Map<String, IOperation> operationMapA = new HashMap<>(); private Map<String, IOperation> operationMapB = new HashMap<>(); public IOperation getOperationA(String id) { IOperation operation = operationMapA.get(id); // 延时加载 if (operation == null) { operation = new OperationImplA(id); operationMapA.put(id, operation); } return operation; } public IOperation getOperationB(String id) { IOperation operation = operationMapB.get(id); // 延时加载 if (operation == null) { operation = new OperationImplB(id); operationMapB.put(id, operation); } return operation; } }
public class Main { public static void main(String[] args) { OperationFactory operationFactory = new OperationFactory(); IOperation operationA = operationFactory.getOperationA("A"); IOperation operationB = operationFactory.getOperationB("B"); operationA.doIt(); operationB.doIt(); } }
如上代码看出,每个id会共享一个对象,多次使用时可以复用对象
4、装饰者模式
装饰者模式也是比较常见的模式,他可以将系统中的功能组件进行包装,提升功能增加的能力,对于一些功能组件,我们可以用装饰者模式为其提高性能能力,具有低耦合的特性,最典型的例子就是JDK中的文件操作,平常经常使用FileInputStream来读取文件内容,FileInputStream是基于字节流的输入,不具备缓冲区的功能,JDK就使用装饰者模式将FileInputStream包装增加了缓冲区的性能优化,即BufferedInputStream,这个例子就是增加了功能组件的性能,还有例子比如Collections.synchronizedMap(),这个对HashMap进行了包装,增加了线程安全的特性。下面看一个简易实现代码
// 业务逻辑接口 public interface IOperation { void doIt(); }
// 业务逻辑的实现 public class OperationImpl implements IOperation { @Override public void doIt() { System.out.println("do it..."); } }
public abstract class AbsOperation implements IOperation { IOperation operation = null; public AbsOperation(IOperation operation) { this.operation = operation; } }
public class OperationPlusA extends AbsOperation { public OperationPlusA(IOperation operation) { super(operation); } @Override public void doIt() { System.out.println("增强功能A。。。"); operation.doIt(); } }
public class OperationPlusB extends AbsOperation { public OperationPlusB(IOperation operation) { super(operation); } @Override public void doIt() { System.out.println("增强功能B。。。"); operation.doIt(); } }
public class Main { public static void main(String[] args) { IOperation operation = new OperationPlusA(new OperationPlusB(new OperationImpl())); operation.doIt(); } }
以上代码可以看出,我们对功能组件进行了两次增强,每次增强都是互相独立的,不会互相影响
5、观察者模式
观察者模式对于性能优化主要在“通知”,当程序A依赖于程序B的执行,如果程序A开线程去轮询程序B来监视程序B的状态,那么会增加系统的负担,所以使用观察者模式来优化达成“通知”这个目的,当程序B执行完毕,就会回调程序A的方法,来告诉程序A自己已经执行好了,免去了开线程去监视的负担,JDK已经帮我们提供了观察者模式的接口,下面使用简单的代码去尝试一下观察者模式
// 观察者 public class ObserverA implements Observer { @Override public void update(Observable o, Object arg) { // 观察者的通知回调 System.out.println("收到。。。"); } }
// 被观察者 public class ObservableA extends Observable { @Override protected synchronized void setChanged() { // 改变状态方法 super.setChanged(); } }
public class Main { public static void main(String[] args) { // 实例化观察者和被观察者 ObserverA observerA = new ObserverA(); ObservableA observableA = new ObservableA(); // 将观察者加入到被观察者的观察列表中 observableA.addObserver(observerA); // 改变状态,然后通知观察者自己改变了 observableA.setChanged(); observableA.notifyObservers(); observableA.setChanged(); observableA.notifyObservers(); } }
观察者需要重写update()方法来定义被通知时要做的事,而被观察者需要重写setChanged方法来改变自己的状态,使用时,先将观察者注册到被观察者中,需要通知时,要调用setChanged()方法改变状态,再调用notifyObservers()方法通知观察者,如果不改变状态是无法发出通知的