单例简介和单例在java的使用方法

今天,我们在学习java时,老师给我们讲了关于单例在java中使用的两种方法。通过在网上查询资料,我对单例有了更深刻的了解。
单例模式,是一种常用的软件设计模式,是设计模式中最简单的形式之一。在他的核心结构中只包含一个被称为单例的特殊类。此模式的目的是使得类的一个对象成为系统的唯一实例。即一个类只有一个对象实例。在现实生活中有很多事物都需要用到单例模式。例如:打印机,一个系统中可以存在多个大一任务,但是只能由一个正在工作的任务。 单例模式的要点有三个; 一是某个类只能 有一个实例; 二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
单例模式的思路:首先私有化构造方法,其次对外提供一个方法可以返回该类的实例。
要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,"阻止"所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
单例的优点:
    实例控制:会阻止其他对象 实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
    灵活性: 类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点:
    开销: 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
    可能的开发开发混淆: 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
    对象生存期: 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
在java中的使用方法
第一种:最体现技术的单例---懒汉式,常用模式
懒汉式即实现延迟加载的单例,为上述饿汉式的优化形式。而因其仍需要进一步优化,往往成为面试考点。
public class Singleton {  
    private static Singleton INSTANCE;  
    private Singleton (){}  
    public static Singleton getInstance() {  
     if (INSTANCE == null) {  
         INSTANCE = new Singleton();  
     }  
     return INSTANCE;  
    }  
}  
这种写法就轻松实现了单例的懒加载,只有调用了 getInstance 方法才会初始化。但是这样的写法出现了新的问题--线程不安全。当多个线程调用 getInstance 方法时,可能会创建多个实例,因此需要对其进行同步。
如何使其线程安全呢?简单,加个 synchronized 关键字就行了
public static synchronized Singleton getInstance() {  
    if (INSTANCE == null) {  
        INSTANCE = new Singleton();  
    }  
    return INSTANCE;  
}  
可是...这样又出现了性能问题,简单粗暴的同步整个方法,导致同一时间内只有一个线程能够调用 getInstance 方法。
因为仅仅需要对初始化部分的代码进行同步,所以再次进行优化:
public static Singleton getSingleton() {  
    if (INSTANCE == null) { // 第一次检查  
        synchronized (Singleton.class) {  
            if (INSTANCE == null) { // 第二次检查  
                INSTANCE = new Singleton();  
            }  
        }  
    }  
    return INSTANCE ;  
}  
执行两次检测很有必要:当多线程调用时,如果多个线程同时执行完了第一次检查,其中一个进入同步代码块创建了实例,后面的线程因第二次检测不会创建新实例。
这段代码看起来很完美,但仍旧存在问题,以下内容引用自黑桃夹克大神的 如何正确地写出单例模式
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
我们只需要将 instance 变量声明成 volatile 就可以了。
public class Singleton {  
    private volatile static Singleton INSTANCE; //声明成 volatile  
    private Singleton (){}  

        if (INSTANCE == null) {                           
    public static Singleton getSingleton() {  
                if (INSTANCE == null) {         
            synchronized (Singleton.class) {  
        }  
                    INSTANCE = new Singleton();  
                }  
            }  
}  
        return INSTANCE;  
    }   
至此,这样的懒汉式才是没有问题的懒汉式。
第二种:最简单的单例---饿汉式
public class Singleton {  
    private static final Singleton INSTANCE = new Singleton();  
    // 私有化构造函数  
    private Singleton(){}  
    public static Singleton getInstance(){  
        return INSTANCE;  
    }  
}  
这种单例的写法最简单,但是缺点是一旦类被加载,单例就会初始化,没有实现懒加载。而且当实现了Serializable接口后,反序列化时单例会被破坏。
实现Serializable接口需要重写 readResolve ,才能保证其反序列化依旧是单例:
public class Singleton implements Serializable {  
    private static final Singleton INSTANCE = new Singleton();  
    // 私有化构造函数  
    private Singleton(){}  
    public static Singleton getInstance(){  
        return INSTANCE;  
    }  
    /** 
     * 如果实现了Serializable, 必须重写这个方法 
     */  
    private Object readResolve() throws ObjectStreamException {  
        return INSTANCE;  
    }  
}  

OK,反序列化要注意的就是这一点。



猜你喜欢

转载自blog.csdn.net/y_blueblack/article/details/79674856