java 程序性能优化《第二章》设计优化 2.1善用设计模式 1 单例模式

java 程序性能优化《第二章》设计优化 2.1善用设计模式 1 单例模式

设计模式是前人工作的总结和提炼。通常,被人们广泛流传的设计模式都是对某一特定问题的成熟的解决方案。如果能合理的使用设计模式,不仅能使系统更容易被他人理解,同时也能使系统拥有更加合理的结构。本节总结归纳了一些经典的设计模式,并详细说明它们与软件性能之间的关系。

2.1.1 单例模式

单例模式是设计模式中使用最为普遍的模式之一。它是一种对象创建模式,用于生产一个对象的具体实现,它可以确保系统中一个类只产生一个实例。在Java语言中,这样的行为能带来两大好处:

(1)对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。

(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

因此对于系统的关键组件和被频繁使用的对象,使用单例模式便可以有效地改善系统的性能。

单例模式的参与者非常简单,只有单例类和使用者两个,如图2.1所示。

表2.1 单例模式角色

角色 作用
单例类 提供单例的工厂,返回单例
使用者 获取并使用单例类

它的基本结构如图2.1所示。

图2.1 单例模式类图

单例模式的核心在于通过一个接口返回唯一的对象实例。一个简单的单例实现如下:

public class Singleton {
	// private级别构造函数
	private Singleton() {
		System.err.println("Singleton is create "); // 创建单例的过程可能会比较慢
	}

	// 静态变量
	private static Singleton instance = new Singleton();

	// 静态方法
	public static Singleton getInstance() {

		return instance;
	}
}

注意代码中的重点标注部分,首先单例类必须要有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化,这点是相当重要的;其次,instance 成员变量和 getInstance() 方法必须是 static 的。

PS:单例模式是非常常用的一种结构,几乎所有的系统中都可以找到它的身影。因此,希望读者可以通过本节,了解单例模式的几种实现方法及其各自的特点。

这种单例的实现方式非常简单,而且非常可靠。它唯一的不足仅是无法对 instance 实例做延迟加载。假如单例的创建过程很慢,而由于 instance 成员变量是 static 定义的,因此在 JVM 加载单例类时,单例对象就会被建立,如果此时,这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到,比如单例类作为String工厂,用于创建一些字符串(该类既用于创建单例Singleton,又用于创建String对象):

public class Singleton {

	// private级别构造函数
	private Singleton() {
		System.err.println("Singleton is create "); // 创建单例的过程可能会比较慢
	}

	// 静态变量
	private static Singleton instance = new Singleton();

	// 静态方法
	public static Singleton getInstance() {

		return instance;
	}

	// 静态方法
	public static void createString() { // 这是模拟单例类扮演其他角色
		System.err.println("createString in Singleton");
	}

	public static void main(String[] args) {
		Singleton.createString();
	}
}

当使用Singleton.createString()执行任务时,程序输出:

Singleton is create 

createString in Singleton

可以看到,虽然此时并没有使用单例类,但它还是被创建出来,这也许是开发人员所不愿意看到的,为了解决这个问题,并以此提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制。

public class LazySingleton {

	// private级别构造函数
	private LazySingleton() {

		System.err.println("LasySingleton is create"); // 创建单例的过程可能会比较慢
	}

	// 静态变量
	private static LazySingleton instance = null;

	// 静态方法
	public static synchronized LazySingleton getInstance() {

		if (instance == null) {

			instance = new LazySingleton();
		}
		return instance;
	}
}

首先,对于静态成员变量 instance 初始值为 null ,确保系统启动时没有额外的负载,其次,在getInstance() 工厂方法中,判断当前单例是否已经存在,若存在则返回,不存在则再建立单例。这里尤其还要注意,getInstance()方法必须是同步的,否则在多线程环境下,当线程1正新建单例时,完成赋值操作前,线程2可能判断 instance 为 null ,故线程2也将启动新建单例的程序,而导致多个实例被创建,故同步关键字是必须的。

使用上例中的单例实现,虽然实现了延迟加载的功能,但和第一种方法相比,它引入了同步关键字,因此在多线程的环境中,它的时耗要远远大于第一种单例模式。以下测试代码就说明了这个问题:

@Override
		public void run() {
			for (int i = 0; i < 100000; i++) {
				LazySingleton.getInstance();
				System.err.println("Spend:"+(System.currentTimeMillis()-beginTime));
			}
		}

开启五个线程同时完成以上代码的运行,使用第一种类型的单例耗时0ms,而是用LazySingleton 却相对耗时约390ms。性能至少相差两个等级。

PS:在本书中,会使用很多类似代码片段用于调试不同代码的执行速度。在不同的计算机上其测试结果很可能与笔者不同。读者大可不必关心测试数据的绝对值,只要观察用于比较的目标代码间的相对耗时即可。

为了使用延迟加载引入的同步关键字反而降低了系统性能,是不是有点得不偿失呢? 为了解决这个问题,还需要 对其他进行改进。

public class StaticSingleton {

	private StaticSingleton() {

		System.err.println("StaticSingleton is create");
	}

	private static class SingletonHolder {

		private static StaticSingleton instance = new StaticSingleton();
	}

	public static StaticSingleton getInstance() {

		return SingletonHolder.instance;
	}

}

在这个实现中,单例模式使用内部类来维护单例的实例,当StaticSingleton 被加载时,其内部类并不会别初始化,故可以确保StaticSingleton 类被载入JVM时,不会初始化这个单例类,而当getSingleton() 方法被调用时,才会加载 SingletonHolder ,从而初始化 instance 。 同时,由于实例的建立是在类加载时完成,故天生对多线程友好, getInstance() 方法也不需要使用同步关键字。因此,这种实现方式同时具备以上两种实现的优点。

PS:使用内部类的方式实现单例,既可以做到延迟加载,也不必使用同步关键字,是一种比较完善的实现。

通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了。但仍然有例外情况,可能导致系统生成多个实例,比如在代码中,通过反射机制,强行调用单例类的私有构造函数,生成多个单例,考虑到情况的特殊性,本书中不对这种极端的方式进行讨论。但仍有些合法的方法,可能导致系统出现多个单例类的实例。

一个可以被串行的实例:

public class SerSingleton {

	String name;

	private SerSingleton() {

		System.err.println("StaticSingleton is create");	//创建单例的过程可能会比较慢

		name = "SerSingleton";
	}

	private static SerSingleton instance = new SerSingleton();

	public static SerSingleton getInstance() {

		return instance;
	}

	public static void createString() {

		System.err.println("createString in Singleton ");
	}

	private Object readResolve() {	//阻止生成新的实例,总是返回当前对象

		return instance;
	}
}

测试代码如下:

@Test
	public void test() throws Exception {
		SerSingleton s1 = null;
		SerSingleton s = SerSingleton.getInstance();
		// 先将实例串行到文件
		FileOutputStream fos = new FileOutputStream("SerSingleton.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s);
		oos.flush();
		oos.close();
		// 从文件读取原有的单例类
		FileInputStream fis = new FileInputStream("SerSingleton.txt");
		ObjectInputStream ois = new ObjectInputStream(fis);
		s1 = (SerSingleton) ois.readObject();

		Assert.assertEquals(s,s1);

使用一段测试代码测试单例的串行化和反串行化,当去掉 SerSingleton 代码中加粗的 readReslove() 函数时,以下测试代码抛出异常:

junit.framework.AssertionFailedError:exected:javatuning.ch2.singleton.seralization.SerSingleton@5224ee
but was:javatuning.ch2.singleton.serialization.SerSingleton@18fe7c3

说明测试代码中 s 和 s1 指向了不同的实例,在反序列化后生成了多个对象实例,而加上 readReslove() 函数的,程序正常退出。说明,即使经过反序列,仍然保持了单例的特征。事实上,在实现了私有的readReslove() 方法后,readObject() 已经形同虚设,它直接使用 readReslove() 替换了原本的返回值,从而在形式上构造了单例。

PS:序列化和反序列化可能会破坏单例。一般来说,对单例进行序列化和反序列化的场景并不多见,但如果存在,就要多加注意。

时间: 2024-09-01 11:36:50

java 程序性能优化《第二章》设计优化 2.1善用设计模式 1 单例模式的相关文章

《Java程序性能优化》学习笔记 设计优化

第一章 Java性能调优概述 1.性能的参考指标   执行时间:   CPU时间:   内存分配:   磁盘吞吐量:   网络吞吐量:   响应时间: 2.木桶定律   系统的最终性能取决于系统中性能表现最差的组件,例如window系统内置的评分就是选取最低分.可能成为系统瓶颈的计算资源如,磁盘I/O,异常,数据库,锁竞争,内存等.   性能优化的几个方面,如设计优化,Java程序优化,并行程序开发及优化,JVM调优,Java性能调优工具的使用等. 3.Amdahl定律   加速比=优化前系统耗

java 程序性能优化《第二章》设计优化 2.1善用设计模式 2 代理模式

java 程序性能优化<第二章>设计优化 2.1善用设计模式 2 代理模式 代理模式也是一种很常见的设计模式.它使用代理对象完成用户请求,屏蔽用户对真实对象的访问.就如同现实中的代理一样,代理人被授权执行当事人的一些适宜,而无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信.而事实上,代理人是要有当事人的授权,并且在核心问题上还需要请示当事人. 在现实中,使用代理的情况很普遍,而且原因也很多.比如,当事人因为某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而需要

Java 程序性能优化《第一章》Java性能调优概述 1.2性能调优的层次

Java 程序性能优化<第一章>1.2性能调优的层次 为了提升系统性能,开发人员开始从系统各个角度和层次对系统进行优化.除了最常见的代码优化外,在软件架构上.JVM虚拟机层.数据库以及操作系统层面都可以通过各种手段进行优化,从而在整体上提升系统的性能. 1.2.1 设计调优 设计调优处于所有调优手段的上层,它往往需要在软件开发之前进行.在软件开发之处,软件架构师就应该评估系统可能存在的各种潜在问题,并给出合理的设计方案.由于软件设计和架构对整体质量有决定性的影响,所以,设计调优对系统性能的影响

Java 程序性能优化《第一章》Java性能调优概述 1.3基本调优策略和手段

Java 程序性能优化<第一章>1.3基本调优策略和手段 存在性能问题的系统,十之八九是由于某一系统瓶颈导致的.只要找到该性能瓶颈,分析瓶颈的形成原因,对症下药,使用合理的方法解决系统瓶颈,就能从根本上提升性能.所以,系统性能优化的最主要目的就是查找并解决性能瓶颈问题.但同时值得注意的是,性能优化往往会涉及对原有的实现进行较大的修改,因此,很难保证这些修改不引入新的问题.所以,在性能优化前,需要对性能优化的目标.方法进行统筹的安排. 1.3.1 优化的一般步骤 对软件系统进行优化,首先需要有明

Java 程序性能优化《第一章》Java性能调优概述 1.4小结

Java 程序性能优化<第一章>1.4小结 通过本章的学习,读者应该了解性能的基本概念及其常用的参考指标.此外,本章还较为详细的介绍了与性能调优相关的两个重要理论--木桶原理以及Amdahl定律. 根据木桶原理,系统的最终性能总是由系统中性能最差的组件决定的.因此,改善该组件的性能对提升系统整体性能有重要的作用.而根据Amdahl定律,可以知道只是增加处理器数量对提升系统性能并没有太大的实际意义,必须同时提高程序的并行化比重. 本章还简要的介绍了在软件开发和维护过程中可以进行性能优化的各个阶段

Java 程序性能优化《第一章》Java性能调优概述 1.1性能概述

Java 程序性能调优<第一章>Java性能调优概述(1.1性能概述) 1.1 性能概述 为什么程序总是那么慢?它现在到底在干什么?时间都花在哪里了?也许,你经常会抱怨这些问题,如果是这样,那说明你的程序出了性能问题.和功能问题相比,性能问题在有些情况下,可能不算什么大问题,将就将就,也就过去了.但是严重的性能问题会导致程序瘫痪.假死.直至崩溃.本书就先来认识性能的各种表现和指标. 1.1.1 看懂程序的性能 对客户端程序而言,拙劣的性能会严重影响用户体验,界面停顿.抖动.响应迟钝等问题会遭到

Java程序性能优化(辛苦了几个小时,还经历了一次停电,我真是命苦!)

程序|性能|优化 Java程序性能优化 一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子:import java.util.Vector;class CEL {    void method (Vector vector) {        for (int i = 0; i < vector.size (); i++)  // Violation            ; //

智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )

原文:智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 ) SQL Optimizer for SQL Server 帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句   SQL Optimizer for SQL Server 让 SQL Server DBA或者T-SQL开发人员能够主动地识别潜在的SQL性能问题,通过扫描和分析SQL语句进行人工智能自动SQL优化.Dell SQL Opt

Java程序性能优化

一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util.vector; class cel { void method (vector vector) { for (int i = 0; i < vector.size (); i++) // violation ; // ... } } 更正: class cel_fixed { void metho