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

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

· 在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 。

时间: 2024-10-23 21:39:50

深刻理解Java中单例模式的实现的相关文章

深刻理解Java中形参与实参,引用与对象的关系

声明:本博客为原创博客,未经允许,不得转载!原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道,在Java中,除了基本数据类型之外,其他的都是引用类型,当它们作为函数参数时,传递的也是引用,通过引用可以改变对象的值,很多人便因此而忽略形参与实参,引用与对象的关系问题.废话不多说,先看下面一个例子: import java.util.*; public class Student { private String

深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因

声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/details/26744661),看代码和提问.讨论都更方便. Java中final的作用主要表如今三方面:修饰变量.修饰方法和修饰类.以下就从这两个方面来解说final的作用.在文末从final及类的设计安全性出发,论述了Java中String为何要被设计成不可变类. 1.final修饰变量 fina

深刻理解Java中的String、StringBuffer和StringBuilder的区别

首先简单地来梳理一下Java中String.StringBuffer和StringBuilder各自的含义. 1.String类 首先,它是线程安全的,即可以用于多线程编程中: 其次,String类的对象是不可变的,即在定义时就确定了,类似String str="Hello";str+="Java";的语句其实是生成了新的对象,只是我们未察觉到而已.但是注意在大量的字符串新建对象时消耗就很可观,这时必须考虑采用StringBuffer或StringBuilder,否

深刻理解Java中的String、StringBuffer和StringBuilder的差别

声明:本博客为原创博客,未经同意.不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(链接为http://blog.csdn.net/bettarwang/article/details/26412497),看代码和提问.讨论都更方便. 首先简单地来梳理一下Java中String.StringBuffer和StringBuilder各自的含义. 1.String类 首先.它是线程安全的,即能够用于多线程编程中. 其次,String类的对象是不可变的,即在定义时就确定了,类似St

深刻理解Python中的元类(metaclass)以及元类实现单例模式

深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例模式的那一节有些疑惑.因此花了几天时间研究下元类这个概念.通过学习元类,我对python的面向对象有了更加深入的了解.这里将一篇写的非常好的文章基本照搬过来吧,这是一篇在Stack overflow上很热的帖子,我看http://blog.jobbole.com/21351/这篇博客对其进行了翻译. 一.理解

读深入理解Java中的String(包括JVM)一文总结和提升

读深入理解Java中的String(包括JVM)一文总结和提升 摘要:String作为Java语言中的字符串模拟类,无论是实际的编程工作还是笔试面试过程,都需要我们都String类非常熟悉,对于String类的大部分字符串操作方法,都必须达到熟练运用的程度才行.但是,笔试和面试过程中,面试官往往喜欢问一些String特性相关的题目,来考察面试者对于String基础知识的掌握是否牢固.(本人尚未研读深入理解JVM这本书,分析JVM都是查看网上资料来分析的,若在接下来的内容有分析不到位的地方请见谅和

理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了 实现代码线程的安全性.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分. volatile 写和读的内存语义: 线程 A 写一个 volatile 变量,实质上是线程 A

深入理解Java中的IO

深入理解Java中的IO 引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下: Java IO概要 a.Java IO中常用的类 b.Java流类的类结构图 1.流的概念和作用 2.Java IO所采用的模型  : 3.IO流的分类 4.Java IO流对象 1.输入字节流InputStream 2.输出字节流OutputStream 3.字符输入流Reader 4.字符输出流Write

深刻理解Python中的元类

译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得不太明白,希望大家可以给出一些实际的例子和代码片段以帮助理解,以及在什么情况下需要进行元编程.于是e-satis同学给出了神一般的回复,该回复获得了985点的赞同点数,更有人评论说这段回复应该加入到Python的官方文档中去.而e-satis同学本人在Stack Overflow中的声望积分也高达6