您的位置:首页 > 编程语言 > Java开发

深刻理解Java中单例模式的实现

2015-03-10 20:29 1041 查看
在之前的学习笔记中已经写了一篇关于单例模式的几种不同实现。这篇文章主要是对之前的那篇笔记的补充和加深。

· 在Java语言中使用单例模式能够带来的好处:

(1):对于频繁使用的对象,可以省略创建对象那个所花费的时间,尤其是那些重量级对象的创建,对于重量级对象的创建那可是一笔相当可观的系统开销。

(2):由于new操作的次数减少了,进一步产生的益处就是,对系统内存的使用频率也会降低了,那么这一举措将会减轻GC压力,缩短GC停顿时间。

以上的这两点都为系统性能的优化带来了改善。

单例模式的实现:

简单可靠型:

public class Singleton {
private **static** Singleton instance = new Singleton();

**private** Singleton(){
}

public **static** Singleton getInstance(){
return instance;
}
}


这种单例模式的实现非常简单,而且十分可靠,实例的创建时机是在类加载时由JVM负责创建的。但是这样会带这种实现模式唯一存在的不足:我们无法对instance实例做延时加载。想象一下如下的场景:

当我们创建的单例类在系统中扮演多种角色时,由于单例类实例的创建是交由JVM负责创建的,那么任何主动使用该类的地方都将触发JVM加载该单例类,进而单例对象就会被创建,而不管此时我们是否真的需要使用该类的实例对象。

改进型(一):

public class Singleton {
private **static** Singleton instance = new Singleton();

**private** Singleton(){
}

public **stati**c Singleton getInstance(){
return instance;
}

//该单例类还担任别的角色,负责生成一个随机数
public **static** int generateNumder(){
return (int)Math.random() * 100;
}
}


这个时候当我们使用该类的generateNumer()方法时,我们可能便不希望加载该类的实例。下面我们将引入延迟加载机制来实现单例模式:

完善型:

public class LazySingleton{
private static LazySingleton instance = null;

private LazySingleton(){}

public static synchronized LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}


引入延迟加载机制时一定要考虑多线程环境下系统可能出现的问题,所以关键字synchronized和检查null操作是必不可少的,但是这样一来系统加载实例对象的时耗就变长了。为了优化时耗这一瓶颈,我们可以进行如改进:

public class StaticSingleton{
private StaticSingleton(){}

private static class SingletonHolder{
private static StaticSingleton instance = new StaticSingleton();
}

public static StaticSingleton getInstance(){
return SingletonHolder.instance;
}
}


在上面的实现了,单例模式采用了内部类来负责维护单例的实例对象,当StaticSingleton被加载时,其内部类并不会被初始化(在StaticSingleton类中除了getInstance方法主动使用了内部类SingletonHolder之外,没有别的地方存在主动使用该内部类,所有不会导致内部类初始化)。同时,由于实例的建立交由JVM在类加载时进行创建,所以该种实现天生对多线程就是友好的。这种实现兼具了上面两种实现的优点。

通常情况下,我们采用上面的三种实现方式之一都能实现单例模式,但是任然存在例外情况,可能导致系统生成多个实例,Java中常见的就是反射

单例模式的另一种实现,该种实现也是《Effective Java》中所推荐的,在JDK5.0以后都可以获得支持,采用枚举实现:

四、枚举,《Effective Java》作者推荐使用的方法,优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
*/
public enum EnumSingleton {
INSTANCE;
}


额外的知识:单例模式在序列化和反序列化时的使用:

一个刻串行化的单例模式实现Demo:

public class Singleton implements java.io.Serializable {
private String clazzName;

private static Singleton instance = new Singleton();

private Singleton(){
this.name = "Demo";
}

public static Singleton getInstance(){
return instance;
}

public **static** int generateNumder(){
return (int)Math.random() * 100;
}

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


关于 readResolve 方法的一些知识:引用自

http://download.oracle.com/javase/1.5.0/docs/guide/serialization/spec/input.html

For Serializable and Externalizable classes, the readResolve method allows a class to replace/resolve the object read from the stream before it is returned to the caller. By implementing the readResolve method, a class can directly control the types and instances of its own instances being deserialized. The method is defined as follows:

ANY-ACCESS-MODIFIER Object readResolve()

throws ObjectStreamException;

The readResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller. ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned. The object returned should be of a type that is compatible with all uses. If it is not compatible, a ClassCastException will be thrown when the type mismatch is discovered.

For example, a Symbol class could be created for which only a single instance of each symbol binding existed within a virtual machine. The readResolve method would be implemented to determine if that symbol was already defined and substitute the preexisting equivalent Symbol object to maintain the identity constraint. In this way the uniqueness of Symbol objects can be maintained across serialization.

Note - The readResolve method is not invoked on the object until the object is fully constructed, so any references to this object in its object graph will not be updated to the new object nominated by readResolve. However, during the serialization of an object with the writeReplace method, all references to the original object in the replacement object’s object graph are replaced with references to the replacement object. Therefore in cases where an object being serialized nominates a replacement object whose object graph has a reference to the original object, deserialization will result in an incorrect graph of objects. Furthermore, if the reference types of the object being read (nominated by writeReplace) and the original object are not compatible, the construction of the object graph will raise a ClassCastException.

序列化对象通过流传给调用者,当调用者从ObjectInputStream流中读取序列化对象时,序列化对象返回给调用者之前会先查看是否已经实现这个方法,如果实现那么就返回这个对象的返回值,如果返回值和调用者期望获得的类类型转换不匹配,那么就会报ClassCastException错误。

但是,前提是,在调用者通过流获取序列化对象时,序列化对象必须已经fully constructed,不然序列化对象不会找这个方法。

方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用。ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出ClassCastException 。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: