独一无二的对象:单件模式(单例模式)

本文深入探讨单例模式的实现与应用,解释如何确保一个类仅有一个实例,并提供全局访问点。文章讨论了单例模式在多线程环境下的挑战及解决方案,如延迟加载、急切实例化和双重检查加锁。

距离上一篇文章已经过去两个月了。。。最近突然醒悟自己怎么越来越懒了,每天就打游戏堕落了哈哈哈

最近重新拾起Head First 设计模式,继续开始进阶之路


今天要写的是单件模式(书上是第5章才开始写着单件(不过我听到更多的是单例),放在这里是我觉得比较简单,这也是所有设计模式中最简单的,不过还是不能小看,因为在实现上还是有许多的波折。下面就直接开始!

问题来了: 如何实现一个唯一的对象?这有什么作用?

大佬:有一些对象其实我们只需要实现一个,比如:线程池(threadpool),缓存(cache),对话框,设置注册表的对象,日志对象等。事实上,这些类对象只能有一个实例,如果创造多个实例,就会导致许多问题发生,例如:程序的行为异常、资源使用过量,或是导致不一致的结果。

我:说的有道理,确实有一些只需要一个实例,但这需要整个章节来讲吗,难道不能靠程序员之间约定或是利用全局变量做到?我觉得利用java的静态变量就可以做到。

大佬:许多时候,的确通过程序员之间的约定就可以办到,但如果有更好的办法,大家也乐意接受,就跟其他的模式一样,单件模式也是经得起时间的考验的,可以确保只有一个实例被创建。单件模式也给我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。

我:什么缺点?

大佬:举例来说,如果将一个对象赋值给全局变量,那你必须要在程序一开始就创建好对象,对吧?万一这个对象非常耗费资源,而程序这次的执行过程中又一直没用到它,不就浪费了吗,稍后你会看到,利用单件模式,我们可以在需要时才创建对象。

我:我觉得没什么困难的。

大佬:利用静态类变量,静态方法和适当的访问修饰符,可以做到这一点,但是,不管使用哪一种方法,能够了解单件的运作方式仍然是很有趣的事,单件模式听起来简单,要做得对可不简单。

问题:如何保证一个对象只能被实例化一次?

如何创建一个对象?                           new MyObject();

万一另一个对象想创建MyObject会怎样?可以再次new 吗?  当然可以了。

所以,一旦有一个类,我们是否能够多次的实例化它?  如果是公开的类,就可以。

如果不是的话呢?   如果不是公开类,只有同一个包内的类可以实例化它,但依然可以实例化它很多次。

你知道可以这样做吗?

public class MyObject {   
 private MyObject() {}
}复制代码

有可以使用私有的构造器的对象吗? 有的,在MyObject内的代码是唯一可以调用此构造器的代码。

这时在这个类里面加一个getInstance()方法

public class MyObject {   
 public static MyObject getInstance(){    
    return new MyObject();  
  } 
   private MyObject() { 
   }
}复制代码


这样就可以在其他包实例化该对象了。

现在就需要保证它只能有一个实例被产生了。修改代码


public class MyObject {   
 private static MyObject myObject;   
 public static MyObject getInstance(){  
      if (myObject == null) {       
     myObject = new MyObject();     
   }      
  return myObject;   
 } 
   private MyObject() {   
     getInstance(); 
   }
}复制代码

这样 ,我们就可以实现延迟加载化了。

此时,有另外一个问题出现了,突然有两条线程同时执行了实例化方法,并且创造了两个同样的对象。导致程序执行错误。

处理多线程

只要把getInstance变成同步(synchronized)方法,多线程灾难几乎就可以轻易的解决。

public static synchronized MyObject getInstance(){  
  if (myObject == null) {  
      myObject = new MyObject();  
  } 
   return myObject;
}复制代码

如果不知道synchronized关键字的方法,可以自行了解一下。


这样确实可以解决问题,但是会减低性能,这不就产生了另一个问题了吗,并且只有第一次执行此方法的时候,才需要真正的同步,换句话说,一旦设置好了myObject对象,就不再需要同步这个方法了。之后每次调用这个方法,同步是个累赘。


改善多线程

1、如果getInstance()的性能对应用程序不是很关键,就什么都别做。

2、使用”急切”创建实例,而不是延迟实例化的做法。

如果应用程序总是创建并使用单例实例,或者在创建和运行方面的负担不太繁重,你可能想要在程序启动时就创建此单件

private static MyObject myObject = new MyObject();
public static MyObject getInstance(){  
  return myObject;
}
private MyObject() { 
}复制代码

3、用“双重检查加锁”,在getInstance()中减少使用同步

利用双重检查加锁(double-checked locking) ,首先检查实例是否已经创建了,如果尚未创建,才进行同步,这样一来就只有第一次会同步,这正是我们真正想要的。

private volatile static MyObject myObject;
public static MyObject getInstance(){ 
   if (myObject == null) {     
   synchronized (MyObject.class){     
       if (myObject == null) {       
         myObject = new MyObject();      
      }      
  }
 }   
 return myObject;
}
private MyObject() {}复制代码
volatile  关键字确保当myObject被初始化成实例后,多个线程正确的处理它。复制代码

如果性能是你关心的重点,这个做法可以帮你大大的减少getInstance()的时间耗费。


总结

单件模式(单例) : 确保一个类只有一个实例,并提供全局访问点。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值