第1章 Java性能调优概述
1.1 性能概述
一.性能的参考指标
- 执行时间:一段代码从开始运行到运行结束所使用的时间
- CPU时间:函数或者线程占用CPU的时间
- 内存分配:程序在运行时占用的内存空间
- 磁盘吞吐量:描述I/O的使用情况
- 网络吞吐量:描述网络的使用情况
- 响应时间:系统对某用户行为或者事件作出响应的时间
二.木桶原理与性能瓶颈
木桶原理又称短板理论,其核心思想是一只木桶盛水的多少,并不取决于桶壁上最高的那块木板,而是取决于桶壁上最短的那块木板。
即使系统拥有充足的内存资源和CPU资源,但是,如果磁盘I/O性能低下,那么系统的总体性能是取决于当前最慢的磁盘I/O速度,而不是当前最优越的CPU或者内存。
这种情况下,如果要进一步提升系统性能,优化内存或者CPU资源是毫无用处的,只有提高磁盘I/O性能才能对系统的整体性能进行优化。
此时,磁盘I/O就是系统的性能瓶颈。
根据木桶原理,系统的最终性能取决于系统中性能表现最差的组件。
因此,为了提升系统的整体性能,必须对系统中表现最差的组件进行优化,而不是对系统中表现最好的组件进行优化。
最有可能成为系统瓶颈的计算资源
- 磁盘I/O
- 网络操作:网络操作可能本地磁盘I/O更慢
- CPU:对计算资源要求较高的应用,由于其长时间,不间断的大量占用CPU资源,那么对CPU的争夺将导致性能问题。例如,科学计算,3D渲染等对CPU需求量大的应用便是如此
- 异常:对于Java应用来说,异常的捕获和处理是非常消耗资源的(?)。如果程序高频率地进行异常处理,则整体性能便会有明显下降。
- 数据库:大部分应用程序都离不开数据库,而海量数据的读写操作往往是相当费时的。应用程序需要等待数据库操作完成并返回结果,那么缓慢的同步操作将成为系统瓶颈。
- 锁竞争:对于高并发程序来说,如果存在激烈的锁竞争,对性能无疑是极大的打击。锁竞争将会明显增加线程上下文切换的开销,而且这些开销都是与应用需求无关的系统开销,白白占用宝贵的CPU资源,却不带来任何好处。
- 内存:一般来说,只要应用程序设计合理,内存在读写速度上不太可能成为性能瓶颈。
Amdahl(阿姆达尔)定律
Amdahl定律是计算机科学中非常重要的定律,它定义了串行系统并行化后的加速比的计算公式和理论上限。
加速比=优化前系统耗时/优化后系统耗时
所谓的加速比,就是优化前系统耗时与优化后系统耗时的比值。
加速比越高,表明优化效果越明显。
设加速比为Speedup,系统内必须串行化的程序比重为F,CPU处理器的数量为N,则有
Speedup≤ 1/(F+(1-F)/N
根据Amdahl定律,使用多核CPU对系统进行优化,优化的效果取决于CPU的数量及系统中串行化程序的比重。
CPU数量越多,串行化比重越低,则优化效果越好。仅提高CPU数量而不降低程序的串行化比重,则无法提高系统性能。
1.2性能调优的层次
1.2.1 涉及调优
设计调优处于所有调优手段的上层,往往需要在软件开发之前进行。
1.2.2 代码调优
虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“坏”的实现对系统的影响也是非常大的。
LinkedList和ArrayList在随机访问上的性能相差几个数量级,文件读写的实现,使用Stream方式与JavaNIO方式,性能可能又会相差一个数量级。
1.2.3 JVM调优
注:根据读者为数不多的经历,感觉JVM调优场景很小,基本只存在于面试
1.2.4 数据库调优
合理使用冗余字段,行的水平分隔或者分区表,建索引
1.2.5 操作系统调优
共享内存段,信号量,共享内存最大值,共享内存最小值等都是可以优化的系统资源。
注:读者感觉这种对于开发人员来说可操作性几乎没有
1.3 基本调优策略和手段
1.3.1 优化的一般步骤
- 首先需要有明确的目标,清楚地指出优化的对象和最终目的
- 测试,看是否达到目标
- 查找瓶颈
- 改进实现:修改代码,优化算法,对JVM,操作系统或者数据库调优,改良设计
第2章 设计优化
2.1 善用设计模式
2.1.1 单例模式
确保一个类只产生一个实例。
好处:
- 省去new操作花费的时间,这对于那些重量级对象而言,是一笔非常可观的系统开销
- 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC的压力,缩短GC停顿时间
单例模式的核心在于通过一个接口来返回唯一的对象实例
必要条件:
- private的访问函数
- instance成员变量和getInstance()方法必须是static
public class Singleton {
private Singleton() {
System.out.println("singleton is create");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
这种单例的实现方式非常简单,而且十分可靠,唯一的不足仅是无法对instance做延迟加载。 假如单例的创建过程很慢,而由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立。 如果此时这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。 例如,单例类作为String工厂用于创建一些字符串(该类既用于创建单例,又用于创建String对象)
public class Singleton {
private Singleton() {
System.out.println("singleton is create");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
/**
* 模拟单例类扮演其他角色
*/
public static void createString() {
System.out.println("createString in singleton");
}
}
当使用Singleton.createString()执行任务时,程序输出以下内容:
singleton is create
createString in singleton
可以看到,虽然此时并没有使用单例类,但它还是被创建出来了,这拖慢了程序的速度
为了解决这个问题并提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制。
public class LazySingleton {
private LazySingleton(){
System.out.println("LazySingleton is create");
}
private static LazySingleton instance=null;
public static synchronized LazySingleton getInstance() {
if(instance==null){
instance=new LazySingleton();
}
return instance;
}
}
这里的getInstance()必须是同步的,否则在多线程环境下,当线程1整新建单例完成赋值操作前,线程2可能判断instance为null,故线程2也将启动新建单例的程序,从而导致多个实例被创建,因此同步关键字是必须步骤。
上例中的单例,虽然实现了延迟加载的功能,但和第一种方式相比,它引入了同步关键字,因此在多线程环境中,它的时耗要远远大于第一种单例模式的时耗 为了使用延迟加载引入的synchronized关键字反而降低了系统系统,有点得不偿失
public class StaticSingleton {
private StaticSingleton() {
System.out.println("StaticSingleton is create");
}
private static class SingleHolder {
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingleHolder.instance;
}
}
在这个实现下,单例模式使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保当StaticSingleton类被载入JVM时不会初始化单例类,而当getInstance()方法被调用时才会加载SingletonHolder,从而初始化instance。
同时,由于实例化的建立是在类加载时完成的,故天生对多线程友好。
在代码中通过反射机制,强行调用单例类的私有构造函数也可生成多个单例,但这种是极端情况,一般不考虑。
序列化和反序列化也可能会破坏单例,但不常见
2.1.2 代理模式
代理模式也是一种很常见的设计模式,它使用代理对象完成用户请求,屏蔽用户对真实对象的访问。
在软件设计中,使用代理模式的意图也很多。
- 出于安全考虑,需要屏蔽客户端直接访问真实对象
- 在远程调用中,需要使用代理类处理远程方法调用的技术细节
- 为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的
在本小节中,主要讨论使用代理模式实现延迟加载,从而提升系统的系统和反应速度。
假设某客户端软件有根据用户请求去数据库查询数据的功能,在查询数据库前需要获得数据库连接,软件开启时,初始化系统的所有类,此时尝试获得数据库连接。当数据库有大量类似操作(如xml解析等)存在时,所有这些初始化操作的叠加会使得系统的启动速度变得非常缓慢。为此,可以使用代理模式的代理类封装对数据库查询中的初始化操作,当系统启动时,初始化这个代理类,而非初始化真实的数据库查询类,在此过程中代理类什么都没有做,因此它的构造是相当迅速的。
在系统启动时,将消耗资源最多的地方都使用代理模式分离,这样就可以加快系统的启动速度,从而减少用户的等待时间。而在用户真正做查询操作时,再有代理类单独去加载真实的数据库查询类,从而完成用户的请求。这个过程就是使用代理模式实现了延迟加载
代理模式可以用在多种场合,如用于远程调用的网络代理,以及考虑安全因素的安全代理等。
延迟加载只是代理模式的一种应用场景。
延迟加载的核心思想:如果当前并没有使用这个组件,则不需要真正地初始化它,而是使用一个代理对象替代它原有的位置,只有在真正需要使用的时候,才对它进行加载。
使用代理模式来做延迟加载是非常有意义的:首先,它可以在时间轴上分散系统的压力,尤其是在系统启动时,不必完成所有的初始化工作,从而减少启动时间;其次,对于很多真实主题而言,可能在软件启动直到关闭的整个过程中根本不会被调用,因此初始化这些数据无疑是一种资源浪费。
1.代理模式的结构
| 角色 | 作用 |
|---|---|
| 主题接口 | 定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法 |
| 真实主题 | 真正实现业务逻辑的类 |
| 代理类 | 用来代理和封装真实主题 |
| Main | 客户端,使用代理类和主题接口完成一些工作 |
若系统不使用代理模式,则在启动时就要初始化DBQuery对象,而使用代理模式后,启动时只需要初始化一个轻量级的对象DBQueryProxy即可。
IDBQuery是主题接口,定义代理类和真实类需要对外提供的服务,在本例中定义了实现数据库查询的公共方法,即request()函数。
DBQuery是真实主题,负责实际的业务操作,DBQueryProxy是DBQuery的代理类。
2.代理模式的实现和使用
IDBQuery的实现方式如下:它只有一个request方法
public interface IDBQuery {
String request();
}
DBQuery的实现方法如下:
它是一个重量级对象,构造会比较慢
public class DBQuery implements IDBQuery{
/**
* 构造方法要public,方便DBQueryProxy访问
*/
public DBQuery(){
//可能包含数据库连接等耗时操作
}
@Override
public String request() {
return "request string";
}
}
读者注:DBQuery的构造方法要public,方便DBQueryProxy访问
代理类DBQueryProxy是轻量级对象,创建很快,用于替代DBProxy的位置
public class DBQueryProxy implements IDBQuery {
private DBQuery real = null;
@Override
public String request() {
//在真正需要的时候才创建真实的对象,创建过程可能很慢
if (real == null) {
real = new DBQuery();
}
//在多线程环境下,返回一个虚假类,类似Future模式
return real.request();
}
}
最后,主函数如下。它引入IDBProxy接口,并使用代理类工作
public static void main(String[] args) {
//使用代理
IDBQuery query=new DBQueryProxy();
//在真正访问对象的时候才创建真实对象
query.request();
}
将代理模式用于实现延迟加强在可以有效提升系统的启动速度,对改善用户体验有很大帮助。
3.动态代理介绍
动态代理是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入当前的classLoader。
与静态代理相比,动态代理有诸多好处
- 不需要为真实主题写一个形式上完全一样的封装类(比如上面的DBQueryProxy)。假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事,如果接口有变动,则真实主题和代理类都要修改,不利于系统维护。
- 可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
4.动态代理实现
仍以DBQueryProxy为例,使用动态代理生成动态类,替代上例中DBQueryProxy.
JDK生成动态代理
首先,使用JDK的动态代理生成代理对象需要实现一个处理方法调用的Handler,用于实现处理方法的内部逻辑。
public class JdkDBQueryHandler implements InvocationHandler {
/**
* 主题接口
*/
IDBQuery real = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (real == null) {
real = new DBQuery();
}
return real.request();
}
}
以上代码实现了一个Handler.它的内部逻辑和DBQueryProxy是类似的。在调用真实主题的方法前,先尝试生成真实主题对象,接着需要使用这个Handler生成动态代理对象
/**
* 使用Handler生成动态代理对象
*
* @return
*/
public static IDBQuery createJdkProxy(){
IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{IDBQuery.class}, new JdkDBQueryHandler());
return jdkProxy;
}
以上代码生成了一个实现了IDBProxy接口的代理类,代理类的内部逻辑由JdkDbQueryHandler决定。生成代理类后,有newProxyInstance()方法返回该代理类的一个实例。至此,一个完整的JDK动态代理就完成了。
CGLIB生成动态代理
CGLIB也需要实现一个处理代理逻辑的切入类
public class CglibDBQueryInterceptor implements MethodInterceptor {
IDBQuery real=null;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (real==null) {
//如果是第一次调用,则生成对象
real=new DBQuery();
}
//使用真实主题完成实际操作
return real.request();
}
public static IDBQuery createCglibProxy(){
Enhancer enhancer = new Enhancer();
//指定切入器,定义代理类逻辑
enhancer.setCallback(new CglibDBQueryInterceptor());
//指定实现的接口
enhancer.setInterfaces(new Class[]{IDBQuery.class});
//生成代理类的实例
IDBQuery cglibProxy = (IDBQuery) enhancer.create();
return cglibProxy;
}
}
Javassist生成动态代理
public class JavassistDynDBQueryHandler implements MethodHandler {
IDBQuery real = null;
@Override
public Object invoke(Object o, Method method, Method method1, Object[] objects) throws Throwable {
if (real == null) {
real = new DBQuery();
}
return real.request();
}
/**
* 基于工厂实现
*
* @return
* @throws Exception
*/
public static IDBQuery createJavassitDynProxy() throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
//指定接口
proxyFactory.setInterfaces(new Class[]{IDBQuery.class});
Class<IDBQuery> proxyClass = (Class<IDBQuery>) proxyFactory.createClass();
IDBQuery javassistProxy = proxyClass.newInstance();
//设置handler
((ProxyObject) javassistProxy).setHandler(new JavassistDynDBQueryHandler());
return javassistProxy;
}
/**
* 基于动态代码实现
*
* @return
* @throws Exception
*/
public static IDBQuery createJavassitBytecodeDynProxy() throws Exception {
ClassPool classPool = new ClassPool(true);
//定义类名
CtClass ctClass = classPool.makeClass(IDBQuery.class.getName() + "JavassistByProxy");
//需要实现的接口
ctClass.addInterface(classPool.get(IDBQuery.class.getName()));
//添加构造函数
ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
//添加类的字段信息,使用动态java代码
ctClass.addField(CtField.make("public " + IDBQuery.class.getName() + " real;", ctClass));
String dbQueryName = DBQuery.class.getName();
//添加方法,这里使用动态java代码指定内部逻辑
ctClass.addMethod(CtMethod.make("public String request(){if(real==null){" + "real=new " + dbQueryName + "();}return real.request();}", ctClass));
//基于以上信息生成动态类
Class pc = ctClass.toClass();
//生成动态类的实例
IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance();
return bytecodeProxy;
}
}
*** 与静态代理相比,动态代理能大幅度减少代码行数,并提升系统的灵活性。
使用
public static void main(String[] args) throws Exception {
IDBQuery idbQuery = null;
//jdk
idbQuery= JdkDBQueryHandler.createJdkProxy();
idbQuery.request();
//cglib
idbQuery= CglibDBQueryInterceptor.createCglibProxy();
idbQuery.request();
//javassist
idbQuery= JavassistDynDBQueryHandler.createJavassitDynProxy();
idbQuery.request();
idbQuery= JavassistDynDBQueryHandler.createJavassitBytecodeDynProxy();
idbQuery.request();
}
推荐使用CGLIB.
JDK的动态类创建最快,这是因为在这个内置实现里,defineClass()方法被定义为native实现,故性能高于其他几种实现。但是在代理类的函数调用性能上,JDK的动态代理不如CGLIB和Javassist的基于动态代码的代理,而Javassit的基于代理工厂的代理实现,代理的性能最差,甚至不如JDK的实现。
**** 在实际开发应用中,代理类的方法调用频率通常要远远高于代理类的实际生成频率(相同类的重复生成会使用Cache),故动态代理对象的方法调用性能应该作为性能的主要关注点。
2.1.3 享元模式
享元模式是设计模式中少数几个以提高系统性能为目的的模式。
它的核心思想是:如果在一个系统中存在多个相同的对象,那么基于只需共享一位对象的拷贝,而不必为每一次使用都创建新的对象。
在享元模式中,由于需要构造和维护可以共享的对象,因此享元模式中常常会出现一个工厂类,用于维护和创建对象。
享元模式对于性能的提升主要体现在:
- 可以节省重复创建对象的开销。享元模式维护的相同对象只会被创建一次
- 节省内存。由于创建的对象数量减少,内存的使用也减少。
享元工厂是享元模式的核心,它需要确保系统可以共享相同的对象。一般情况下,享元工厂会维护一个对象列表,当任何组件尝试获取享元类时,如果享元类已经被创建,则直接返回已经存在的享元类,若没有则创建一个新的享元对象,并将它加入到维护队列中。
享元模式是为数不多的只为提升系统性能而生的设计模式,它的主要作用就是复用大对象(重量级对象),以节省内存空间和对象创建时间。
享元模式的一个典型应用是用在SaaS系统中。
以一个人事管理系统的SaaS软件为例,假设公司甲,乙,丙均为这个SaaS系统的用户,每个公司为这套系统的一个租户。每个公司(租户)有100个员工,假设这些公司的所有员工都可以登录这套系统查看自己的收入情况,并且为了系统安全,每个公司(租户)都拥有自己独立的数据库。
为了使系统的设计最合理,便可以共享一个查询(因为一个租户下所有的员工数据都放在一个数据库中,它们共享数据库连接)
这样系统只需要3个享元实例,就足以应对300个员工的查询请求
享元对象接口
public interface IReportManager {
String createReport();
}
财务报表
public class FinancialReportManager implements IReportManager {
protected String tenantId = null;
public FinancialReportManager(String tenantId) {
this.tenantId = tenantId;
}
@Override
public String createReport() {
return "This is a financial report";
}
}
员工报表
public class EmployeeReportReportManager implements IReportManager {
protected String tenantId = null;
public EmployeeReportReportManager(String tenantId) {
this.tenantId = tenantId;
}
@Override
public String createReport() {
return "This is a employee report";
}
}
最为核心的享元工厂类是享元模式的精髓所在,它确保同一个公司(租户)使用相同的对象产生报表。这是相当有意义的,否则系统可能会为每个员工生成各自的报表对象,从而导致系统开销激增。
享元工厂
public class ReportManagerFactory {
Map<String, IReportManager> financialReportManagerMap = new HashMap<>();
Map<String, IReportManager> employeeReportManagerMap = new HashMap<>();
public IReportManager getFinancialReportManager(String tenantId) {
IReportManager reportManager = financialReportManagerMap.get(tenantId);
if (reportManager == null) {
reportManager = new FinancialReportManager(tenantId);
//维护已创建的享元对象
financialReportManagerMap.put(tenantId, reportManager);
}
return reportManager;
}
public IReportManager getEmployeeReportManager(String tenantId) {
IReportManager reportManager = employeeReportManagerMap.get(tenantId);
if (reportManager == null) {
reportManager = new EmployeeReportReportManager(tenantId);
//维护已创建的享元对象
employeeReportManagerMap.put(tenantId, reportManager);
}
return reportManager;
}
}
使用
public static void main(String[] args) {
ReportManagerFactory reportManagerFactory = new ReportManagerFactory();
String tenantId="AAA";
IReportManager reportManager = reportManagerFactory.getFinancialReportManager(tenantId);
System.out.println(reportManager.createReport());
}
ReportManagerFactory作为享元工厂,以租户的ID为索引,维护了一个享元对象的集合,它确保相同租户的请求都会返回同一个享元实例,从而确保享元对象的有效复用。
2.1.4 装饰者模式
装饰者模式拥有一个设计非常巧妙的结构,它可以动态地添加对象功能。
在基本的设计原则中,有一个重要的设计原则叫做合成/聚合复用原则。
根据该原则的思想,代码复用应该尽可能使用使用委托,而不是是继承。这是因为,继承是一种紧密耦合,任何父类的改动都会影响其子类,不利于系统的维护,而委托则是松散耦合,只要接口不变,委托类的改动并不会影响其上层对象。
装饰者模式就充分运用了这种思想,通过委托机制复用系统中的各个组件,在运行时可以将这些功能组件进行叠加,从而构造一个“超级对象”,使其拥有这些组件的功能。
而各个子功能模块被很好地维护在各个组件的相关类中,拥有整洁的系统接口。装饰者模式可以有效分离性能组件和功能组件,从而提升模块的可维护性,并增加模块的复用性。
装饰者(Decorator)和被装饰者(ConconcreteComponent)拥有相同的接口Conponent
被装饰者通常是系统的核心组件,完成特定的功能目标;而装饰者则可以在被装饰者的方法前后加上特定的前置处理和后置处理,增强被装饰者的功能。
装饰者模式的角色
| 角色 | 作用 |
|---|---|
| 组件接口 | 组件接口是装饰者和被装饰者的父类或者接口,定义了被装饰者的核心功能和装饰者需要加的功能点从而完成 |
| 具体组件 | 实现组件接口的核心方法,从而完成某一个具体的业务逻辑。具体组件也是被装饰者 |
| 装饰者 | 实现组件接口,并持有一个具体的被装饰者对象 |
| 具体装饰者 | 具体实现装饰的业务逻辑,即实现被分离的各个增强功能点。各个具体装饰者是可以相互叠加的,从而构成一个功能更强大的具体对象 |
| 装饰者模式的一个典型案例就是对输出结果进行增强。例如:现在需要将某一个结果通过HTML进行发布,那么首先就需要将内容转换为一个HTML文本,同时,由于内容需要在网络上通过HTTP传输,因此还需要为其增加HTTP头。有时情况更复杂,可能还需要添加TCP头。 | |
| 装饰者模式的核心思想在于:无需将所有的逻辑,即核心内容构建、HTML文本构造和HTTP头生成3个功能模块粘合在一起实现。 通过装饰者模式,可以将它们分解为3个几乎完全独立的组件,并在使用时灵活地进行装配。 |
- IPacketCreator即装饰接口,用于处理具体的内容;
- PacketBodyCreator是具体的组件,它的功能是构造要发布信息的核心内容,但是它不负责将其构造成一个格式工整的可直接发布的数据格式;
- PacketHTTPHeaderCreator负责对给定的内容加上HTTP头部;
- PacketHTMLHeaderCreator负责讲给定的内容格式化成HTML文本
IPacketCreator的实现很简单,它是一个单方法接口
public interface IPacketCreator {
String handleContent();
}
PacketBodyCreator用于返回数据包的核心数据
public class PacketBodyCreator implements IPacketCreator{
@Override
public String handleContent() {
//构造核心数据,但不包括格式
return "Content of packet";
}
}
PacketDecorator维护核心组件component对象,它负责告知其子类,其核心业务逻辑应该全权委托component完成,自己仅仅是做增强处理
public abstract class PacketDecorator implements IPacketCreator {
IPacketCreator iPacketCreator;
PacketDecorator(IPacketCreator iPacketCreator) {
this.iPacketCreator = iPacketCreator;
}
}
PacketHTMLHeaderCreator是具体的装饰器,负责对核心发布内容进行HTML格式化操作.需要特别注意的是,它委托具体组件component进行核心业务处理.
public class PacketHTMLDecorator extends PacketDecorator {
PacketHTMLDecorator(IPacketCreator iPacketCreator) {
super(iPacketCreator);
}
@Override
public String handleContent() {
StringBuffer sb = new StringBuffer();
sb.append("<html>");
sb.append("<body>");
sb.append(iPacketCreator.handleContent());
sb.append("</body>");
sb.append("</html>\n");
return sb.toString();
}
}
PacketHTTPHeaderCreator只完成数据包HTTP头部的处理,其余业务处理依然交由内部的component完成.
public class PacketHTTPDecorator extends PacketDecorator {
PacketHTTPDecorator(IPacketCreator iPacketCreator) {
super(iPacketCreator);
}
@Override
public String handleContent() {
StringBuffer sb = new StringBuffer();
sb.append("Cache-Control:no-cache\n");
sb.append("Date:20230430\n");
sb.append(iPacketCreator.handleContent());
return sb.toString();
}
}
使用
public class Main {
public static void main(String[] args) {
IPacketCreator creator = new PacketHTTPDecorator(
new PacketHTMLDecorator(
new PacketBodyCreator()));
String content = creator.handleContent();
System.out.println(content);
}
}
读者注:其实就是不断套娃,将当前处理类需要的前置处理类通过构造方法传递进来. 例如,PacketHTTPDecorator依赖PacketHTMLDecorator先处理,PacketHTMLDecorator依赖PacketBodyCreator先处理.
2.15 观察者模式
观察者模式一种很常用的设计模式.当一个对象的行为依赖另一个对象的状态时,观察者模式相当有用. 若不使用观察者提供的通用结构而实现其类似的功能,则只能在另一个线程中不停地监听对象所依赖的状态. 观察者模式可以在单线程中使某一对象即时得知自身所依赖状态的变化.
- ISubject是被观察对象,它可以增加或者删除观察者;
- IObserver是观察者,它依赖于ISubject的状态变化 当ISubject的状态发生变化时,会通过inform()方法通知观察者. 观察者模式可以用于事件监听,通知发布等场合,它可以确保观察者在不使用轮询监控的情况下及时收到相关消息和事件
| 角色 | 作用 |
|---|---|
| 主题接口 | 指被观察的对象.当其状态发生变化或者某事件发生时,它会将这个变化通知观察者 |
| 具体主题 | 实现主题接口中的方法,如新增观察者、删除观察者和通知观察者,内部维护一个观察者列表 |
| 观察者接口 | 定义观察者的基本方法,当依赖状态发生改变时,就会调用观察者的update()方法 |
| 具体观察者 | 实现观察者接口的update方法,用于具体处理当被观察者状态改变或者某一事件发生时的业务逻辑 |
主题接口的代码:
public interface ISubject {
void attach(IObserver observer);
void detach(IObserver observer);
void inform();
}
观察者接口代码:
public interface IObserver {
void update();
}
具体主题的实现:
public class ConcreteSubject implements ISubject {
Vector<IObserver> observers = new Vector<>();
@Override
public void attach(IObserver observer) {
observers.addElement(observer);
}
@Override
public void detach(IObserver observer) {
observers.removeElement(observer);
}
@Override
public void inform() {
for (IObserver observer : observers) {
observer.update();
}
}
}
具体观察者的实现:
public class ConcreteObserver implements IObserver {
@Override
public void update() {
System.out.println("observer receives information");
}
}
2.2 常用的优化组件和方法
2.2.1 缓冲
缓冲区(Buffer)是一块特定的内存区域.开辟缓冲区域的目的是通过协调应用程序上下层之间的性能差异,提升系统的性能.
缓冲可以协调上层组件和下层组件的性能差异.当上层组件的性能优于下层组件的性能时,可以有效减少上层组件等待下层组件的时间.
基于这样的结构,上层应用组件不需要等待下层组件真实地接收全部数据,即可返回操作,加快了上层组件的处理速度,从而提升系统的整体性能.
缓冲最常用的场景就是提高I/O的速度. 为此,JDK内不少I/O组件都提供了缓冲功能.
例如,FileWriter
public static void main(String[] args) throws IOException {
final Integer CIRCLE = 10000000;
StopWatch stopWatch = new StopWatch();
stopWatch.start("writer");
Writer writer = new FileWriter("/Users/pengchunkao/logs/writer.txt");
for(int i=1;i<CIRCLE;i++){
writer.write(i);
}
writer.close();
stopWatch.stop();
stopWatch.start("bufferedWriter");
Writer bufferedWriter = new BufferedWriter(new FileWriter("/Users/pengchunkao/logs/bufferedWriter.txt"));
for(int i=1;i<CIRCLE;i++){
bufferedWriter.write(i);
}
bufferedWriter.close();
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
普通的Writer0.7秒,加了缓存的BufferedWriter0.1秒
2.2.2 缓存
2.2.3 对象复用--池
2.2.4 并行替代串行
2.2.5 负载均衡
2.2.6 时间换空间
2.2.7 空间换时间
读者注:本书剩下的章节感觉读其他书籍更合适