2.1 善用设计模式 23 (1)
1. 设计模式好处;
2.1.1 单例模式 23 (6)
1. 单例模式是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例;
2. 两大好处:a、对于频繁创建的对象,可以省略创建对象所花费的时间;b、new操作减少,因而对系统内存的使用频率也会降低,降低GC压力,缩短GC停顿时间;
3. 单例模式的参与者:单例类和使用者;
4. 第一种实现方式:私有默认构造器,静态getInstance方法;这种实现方式简单、可靠,但不能延迟加载;
5. 第二种实现方式:私有默认构造器,创建时判断对象是否为空,提供同步的getInstance方法;这种实现方式虽然能够延迟加载,但引入了同步,多线程时时耗远远大于第一种实现;
6. 第三种实现方式:私有默认构造器,私有静态内部类里边创建私有静态的实例,静态getInstance方法;当getInstance被调用时,才会加载SingletonHolder,从而初始化instance,同时,由于实例的建立是在类加载时完成的,故天生对多线程友好;
第一种实现:
这种单例的实现方式简单、有效,但是不能延迟加载;
第二种实现方式,引入延迟加载:
第三种实现方式:
2.1.2 代理模式 28 (17)
1. 使用代理对象完成用户请求,屏蔽用户对真实对象的访问;
2. 使用代理模式的意图:安全原因、远程调用、延迟加载(提升系统的性能和反应速度);
3. 代理模式的主要参与者:主题接口、真实主题、代理类、客户端;
4. 延迟加载的常用案例:数据库连接、系统启动,hibernate框架;
5. 延迟加载的核心思想:如果当前并没有使用该组件,则不需要真正地初始化它,使用一个代理对象替代它原有的位置,只要在真正需要使用的时候,才对它进行加载;
6. 动态代理是指在运行时,动态生成代理类;
7. 和静态代理相比,动态代理的好处:1、不需要为真实主题写一个形式上完全一样的封装类,2、使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑;
8. 生成动态代理类的方法有4种:JDK自带的动态代理、CGLIB、javassist、ASM库;
9. JDK自带的动态代理使用简单,但功能较弱;
10. CGLIB和javassist是高级的字节码生成库,性能比JDK自带的好,且功能强大;
11. ASM库低级的字节码生成工具,对开发人员要求较高,性能没有量级的提升,可维护性差,只在性能要求苛刻的地方使用;
12. JDK的动态代理实现:实现InvocationHandler接口,通过Proxy.newProxyInstance方法创建代理;
13. CGLIB的实现(javassist类似):实现MethodInterceptor接口,new Enhancer-->setCallback-->setInterfaces-->create;
14. javassist有两种方式创建:1、使用代理工厂;2、通过使用动态代码创建;
15. javassist代理工厂方式实现:实现MethodInterceptor接口,new ProxyFactory-->setInterfaces-->createClass-->newInstance;
16. javassist使用动态代码方式创建,相当灵活,甚至可以在运行时生成业务逻辑;
17. javassist动态代码方式实现:ClassPool,CtClass;
jdk的动态代理实现:
CGLIB的实现:
javassist的代理工厂实现:
javassist动态java代码方式实现:
几种实现方式性能测试:
2.1.3 亨元模式 37 (8)
1. 亨元模式以提高系统性能为目的,核心思想是如果一个系统中存在多个相同的对象,那么只需共享一份对象的拷贝,而不必每次都创建新的对象。
2. 亨元模式对性能提升的主要帮助有两点:(1)、可以节省重复创建对象的开销,(2)、由于创建对象的数量减少,所以对内存的需求也减少,GC压力降低;
3. 亨元模式的主要角色有4个:亨元工厂、抽象亨元、具体亨元类和主函数;
4. 一般情况下,亨元工厂会维护一个对象列表,如果请求的对象已经创建,则直接返回,如果没有,则创建对象加入到维护队列中;
5. 亨元模式的典型应用:SAAS系统(Software As A Service,目前比较流行的一种软件应用模式);
6. 以一个人事管理系统的SAAS系统为例,公司甲、乙、丙都为这个SAAS系统的用户,则3个亨元实例,就足以应付300个员工的查询请求了;
7. 亨元模式和对象池的最大不同是亨元模式的对象都是不可以相互替代的,对象池的对象都是等价的,如数据库连接池中的数据库连接;
8. 报表实例;
实例:
2.1.4 装饰者模式 40 (7)
1. 装饰者模式可以动态添加对象功能;
2. 有一条重要的设计准则叫做合成/聚合复用原则:代码复用应该尽可能使用委托,而不是使用继承;因为继承是一种紧密耦合,任何父类的改动都会影响其子类,不利于系统维护。而委托则是松散耦合,只要接口不变,委托类的改动并不会影响其上层对象;
3. 装饰者模式通过委托机制,复用系统中的各个组件,在运行时,可以将这些功能组件进行叠加,从而构造一个“超级对象”,使其拥有所有这些组件的功能;
4. 装饰者模式的好处:可以有效地分离性能组件和功能组件,从而提升模块的可维护性并增加模块的复用性;
5. 装饰者模式的主要角色有4个:组件接口、具体组件、装饰者、具体装饰者;
6. 装饰者的典型案例是对输出结果进行增强,对输出内容转化为HTML,增加HTTP头;
7. JDK中不少组件用装饰者模式实现,比如OutputStream和InputStream类族的实现;
案例:
jdk中的不少组件用装饰者模实现;
2.1.5 观察者模式 46 (5)
1. 当一个对象的行为依赖于另一个对象的状态时,适用于观察者模式,如果不用,则一般需要在另一个线程中不停监听对象所依赖的状态,增加系统的负担;
2. 观察者模式的意义在于:可以在单线程中,使某一个对象,及时得知自身所依赖的状态变化。
3. 观察者模式可以用于事件监听、通知发布等场合;
4. 观察者模式的主要角色有4个:主题接口、具体主题、观察者接口、具体观察者;
5. JDK有一套观察者模式的实现,java.util.Observable类和java.util.Observer接口,
实例:
2.1.6 Value Object模式 50 (2)
1. 在J2EE软件开发中,通常会对系统模块进行分层;
2. Value Object模式提倡将一个对象的各个属性进行封装,将封装后的对象在网络中传递,从而拥有更好的交互模型,并且减少网络通信数据,从而提高系统性能;
2.1.7 业务代理模式 53 (2)
1. 业务代理模式是将一组由远程方法调用构成的业务流程,封装在一个位于展示层的代理中;
2. 业务代理模式将一些业务流程封装在前后台系统,为系统性能优化提供了基础平台,在业务代理中,不仅可以复用业务流程,还可以视情况为展示层组件提供缓存等功能,从而减少远程方法调用次数,降低系统压力;
2.2 常用优化组件和方法 56 (2)
1. 组件:缓冲和缓存;
2. 常用的5个优化思想:池化对象、并行代替串行、负载均衡、时间换空间、空间换时间;
2.2.1 缓冲(Buffer) 56 (6)
1. 缓冲区是一块特定的内存区域;
2. 开辟缓冲区的目的是缓解应用程序上下层之间的性能差异,提高系统的性能。
3. 日常生活中,缓冲区的一个典型应用是漏斗;
4. 缓冲可以协调上层组件和下层组件的性能差,当上层组件性能优于下层组件时,可以有效较少上层组件对下层组件的等待时间;
5. 缓冲最常用的场景就是提高I/O的速度;
6. 案例:使用缓冲区提升动画显示效果(先在内存中画圆,然后一次性显示出来);
案例:使用缓冲区提升动画显示效果
2.2.2 缓存(Cache) 59 ()
1. 缓存也是一块为提升系统性能而开辟的内存空间;缓存的主要目的是暂存数据处理结果,并提供下次访问使用。
2. 案例:(1)、目前流行的几种浏览器都会在本地缓存远程的页面,从而减少远程HTTP访问次数;(2)、在服务器端的系统开发中,设计人员可以为一些核心API加上缓存,从而提供系统的整体性能;
3. 最为简单的缓存实现是使用HashMap实现,会有很多问题:何时清理无效的数据、如何防止缓存数据过多而导致内存溢出等;
4. 稍好的解决方案是直接使用WeakHashMap,它使用弱引用维护一张哈希表,从而避免了潜在的内存溢出问题,但作为专业的缓存,功能略有不足;
5. Java的3种缓存框架:EHCache、OSCache和JBOSSCache等;
6. EHCache缓存出自Hibernate,是Hibernate框架默认的数据缓存解决方案;
7. OSCache缓存是由OpenSymphony设计的,它可以用于缓存任何对象,甚至缓存部分JSP或HTTP请求;
8. JBOSSCache是由JBOSS开发,可用于JBOSS集群间数据共享的缓存框架;
9. EHCache缓存配置实例:定义了一个默认的Cache模板,定义了两个缓存,名字分别是cache1和cache2;
10. 什么时候使用缓存:在频繁使用且重负载的函数实现中,加入缓存,以提高它在频繁调用时的性能;
11. 为方法加入缓存有两种方式:(1)、硬编码方式,在方法中加入缓存的处理逻辑,缺点是缓存组件和业务代码紧密结合,依赖性强;(2)、动态代理,好处是在业务层,无需关注对缓存的操作,缓存操作代码被完全独立并隔离,并且对一个新的函数方法加入缓存不会影响原有方法的实现,是一种比较灵活的软件结构;
12. 实例:整数做因式分解;
实例:对整数做因式分解;
2.2.3 对象复用--“池” 63 (8)
1. 对象池化:如果一个类被频繁请求使用,那么不必每次都生成一个实例,可以将这个类的一些实例保存在“池”中,待需要使用的时候直接从池中获取;
2. 实现上可能是数组、链表、任何集合类;常见的案例有:线程池和数据库连接池;
3. 目前应用较为广泛的数据库连接池组件有C3P0、dbcp、Proxool;C3P0是伴随Hibernate一起发布的,与Hibernate联系紧密的数据库连接池;
4. 在JDK中,new操作的效率是相当高的,不需要担心频繁的new操作对系统有性能影响。但是new操作时所调用的类构造函数可能是非常费时的,对于这些对象,可以考虑池化;
5. 实际开发中,不用自己实现对象池,在Apache中,已经提供了一个Jakarta Commons Pool对象池组件,可以直接使用,主要有:接口ObjectPool、接口PoolableObjectFactory;
6. Jakarta Commons Pool中内置定义了3个对象池,分别是StackObjectPool、GenericObjectPool、SoftReferenceObjectPool。
7. 实例:对象池工厂;
8. 只有对重量级对象使用对象池技术才能提供系统性能,对轻量级的对象使用对象池,可能反而降低系统性能;
实例:对象池工厂
2.2.4 并行替代串行 69 (1)
1. java对多线程的支持为多核计算提供了强有力的保障。
2.2.5 负载均衡 69 (8)
1. 对大型应用来说,系统负载可能非常重,以网站应用为例,如果并发数很多,则单台计算机就无法承受,此时,为保障应用程序的服务质量,需要使用多台计算机协同工作,将系统负载尽可能均匀分配到各个计算机节点上;
2. 典型实现是Tomcat集群:使用Apache作为负载分配器,将请求转向各个Tomcat服务器;
3. 在使用tomcat集群时,有两种基本的session共享模式:黏性Session模式和复制Session模式;
4. 黏性session模式:所有的session信息被平均分配到各个tomcat节点上,以实现负载均衡,但一旦一个节点宕机,它所维护的session将丢失,不具备高可用性,且同一个用户只能与一台tomcat交互,因为其他tomcat节点不保存这个用户信息;
5. 复制session模式:所有的session在所有的节点上保持一致。当一个节点上的session信息被修改,这个session会被广播到其他tomcat节点上,以保持session同步。缺点是容易引起网络繁忙,影响系统效率;
6. 成熟的高可用性系统解决方案:Terracotta,它是java开源软件中,一款跨JVM虚拟机专门用于分布式缓存的框架,使用它也可以实现Tomcat的session共享;
7. Terracotta是一款企业级的、开源的、JVM层的集群解决方案,它可以实现分布式对象共享、分布式缓存、分布式session等功能,可以作为负载均衡、高可用性的解决方案;官网:http://terracotta.org;
8. terracotta实例:分布式对象共享、session共享;
terracotta应用案例:
2.2.6 时间换空间 75 (4)
1. 由于系统资源是有限的,为了在有限的系统资源内,达成某些特定的性能目标,就需要使用时间换空间或空间换时间的方法。
2. 时间换空间常用用于嵌入式设备,或者内存、硬盘空间不足等情况,通过使用牺牲CPU的方式,获得原本需要更多内存或者硬盘空间才能完成的工作;
3. 一个非常简单的时间换空间的算法:实现了a、b两个变量的值交换。常用的算法是采用中间变量,而引入额外的变量意味着要使用更多的空间,以下算法,则可以免去中间变量,而达到变量交换的目的,代价是引入更多的CPU运算;
a=a+b;
b=a-b;
a=a-b;
4. 另一个比较有用的例子是对无符号数整数的支持。在java语言中,不支持无符号整数,这意味着当需要无符号的byte时,需要使用short代替,这也意味着空间上的浪费。使用位运算符模拟无符号byte;
2.2.7 空间换时间 76 (3)
1. 与时间换空间相反,空间换时间是尝试使用更多的内存或者磁盘空间换取CPU资源或者网络资源等,通过增加系统的内存消耗,来加快程序的运行速度;
2. 典型案例是缓存。空间换时间是一种设计思路,除了缓存外,在一些算法中,也可以使用这样的技术。
3. 空间换时间排序方法;