单例简介和单例在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 (){}             

    public static Singleton getSingleton() { 

           if (INSTANCE == null) {        

             synchronized (Singleton.class) {  

                   if (INSTANCE == null) {       

                           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,反序列化要注意的就是这一点。

 
 
 
 
 
 
 
 
 
 
 
 
 
 

 


 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值