单例模式

本文详细介绍了单例模式的概念、使用要点及其多种实现方式,包括饿汉式、懒汉式、双重检查锁定(DCL)、静态内部类及枚举等,并讨论了单例模式的优缺点及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一。定义
      GOF对单例模式(Singleton Pattern)的定义: 保证一个类,只有一个实例存在,而且可自行实例化,同时提供能对该实例加以访问的全局访问方法。
二。使用要点
  单例模式是一种对象创建型模式,使用单例模式,可以保证一个类只能生成唯一的实例对象。也就是说在整个程序空间中,该类只存在一个实例对象。
  单例模式的要点有三:
(1)构造函数不对外开放,一般为private
(2)通过一个静态方法或枚举返回单例类对象
(3)确保单例类的对象有且只有一个,尤其是在多线程的环境下
(4)确保单例类对象在反序列化时不会重新构建对象
  通过将单例类的构造函数私有化,使客户端不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获得这个单单例对象的过程中需要确保线程安全,即在多线程环境下,构造单例类的对象也是有且只有一个。

  单例模式适合于一个类只由一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被普及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是单例模式的应用。
三。版本实现  
(1)饿汉式,也就是当类加载进来的就立即实例化,但这种方式比较消耗计算机资源
********************************************************************************
package com.yinazh.designPattern.Singleton;
public class SingletonOne{
//在类被加载进入内存的时候就创建单一的GG对象
private static final SingletonOne instance = new SingletonOne();
//构造函数私有化
private SingletonOne(){}
    //提供一个全局的静态方法
public static SingletonOne getInstance(){
return instance;
}
}
********************************************************************************
(2)懒汉式,在单线程下能够很好的工作,但是在多线程下存在线程安全问题:
********************************************************************************
package com.yinazh.designPattern.Signleton;
//懒汉式,在需要的时候才实例化
public class SingletonTwo{
//对单例本身引用的名称
private static SingletonTwo instance;
//构造函数私有化
private SingletonTwo(){}
//提供一个全局的静态方法
public static SingletonTwo getInstance(){
if(instance == null){
instance = new SingletonTwo();
}
return instance;
}
}
********************************************************************************
(3)为解决多线程问题,采用了对函数进行同步的方式,但是比较浪费资源,因为每次都要进行同步检查,而实际中真正需要检查只是第一次实例化的时候,具体代码如下:
********************************************************************************
package com.yinazh.designPattern.Singleton;
//对函数进行同步
public class SingletonThree{
//对单例本身引用的名称
private static SingletonThree instance;
private SingletonThree(){}
//提供一个全局静态方法,使用同步方法
public static synchronized SingletonThree getInstance(){
if(instance == null){
instance = new SingletonThree();
}
return instance;
}
}
********************************************************************************
说明:懒汉式的单例只有在使用的时候才会被实例化,但是每次调用getInstance()都要进行同步,造成不必要的同步开销。
(4)DCL(Double CHeck Lock)实现单例,既解决了懒汉式的多线程问题,又解决了资源浪费线程,代码如下:
********************************************************************************
package com.yinazh.designPattern.Singleton;

public class SingletonFour{
private static SingletonFour instance;
private SingletonFour(){}
public static SingletonFour getInstance(){
if(instance == null){
synchronized(SingletonFour.class){
if(instance == null){
instance = new SingletonFour();
}
}
}
return instance;
}
}
********************************************************************************
说明:既能够在需要时才初始化单例,又能保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
  本程序在getInstance()方法上,对instance进行了2次判空操作,第一次主要是为了避免不必要的同步,第二层则是为了在null的情况下创建实例。对第二种情况说明,假设线程A执行到instance = new SingletonFour();语句,这句代码会被编译成多条汇编指令,大致做了3件事:
a. 给SingletonFour单例分配内存
b. 调用SingletonFour的构造函数,初始化成员字段
c. 将instance对象指向分配的内存空间(此时instance就不为null了)
  由于java编译器允许处理器乱序执行,以及JDK1.5之前JMM中Cache,寄存器到主内存回写顺序的规定,上面的b,c操作的顺序无法保证。也就是说,可以是abc,也可以是acb。此时,如果是后者,并且c执行完毕,b未执行,被切换到B线程上,这时候instance因为已经在线程A内执行过第三点了,instance已经非空了,所以,线程B取走instance,再使用时就会报错。
  而在JDK1.5之后,调整了JVM,具体化了volatile关键字,因此只需要将instance的定义改为:private volatile static SingletonFour instance; 就可以保证instance 对象每次都是从主内存中读取,就可以使用第4中写法来完成单例模式。
DCL实现的单例资源利用率高,但第一次加载时反应稍慢,这种是使用最多的单例模式。

四。静态内部类单例模式
 ********************************************************************************
package com.yinazh.designPattern.Singleton;

public class SingletonFive{
private SingletonFive(){}
public static SingletonFive getInstance(){
returnSingletonHolder.instance;
}

private static class SingletonHolder{
private static final SingletonFive instance = new SingletonFive();
}
}
********************************************************************************
当第一次加载SingletonFive类时并不会初始化instance,只有第一次调用getInstance()方法时,才会导致instance的初始化。这种方法不仅能够确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化。
五。枚举单例
********************************************************************************
public enum SingletonSex{
INSTANCE;
public void doSomething(){
...
}
}
********************************************************************************
 枚举在java中与普通类一样,不经能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
 上面说的几种单例,在一种情况下会出现重新创建对象的情况,那就是反序列化。反序列化时,可以通过特殊的途径去创建新的实例,会调用该类的构造函数。
   反序列化提供了一个钩子函数,类中具有一个私有的,被实例化的方法readResolved(),这个方法可让开发者控制反序列化。例如在上面的例子中要杜绝单例对象被反序列化时重新生成对象,那么必须加入如下方法,将instance对象返回,而不是默认的重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即是反序列化,也不会重新生成新的实例。
********************************************************************************
private Object readResolved() throws ObjectStreamException{
return instance;
}
********************************************************************************
六。使用容器实现单例模式
********************************************************************************
public class SingletonManager{
private static Map<String, Object> objMap = new HashMap<String, Object>();
private SingletonManager(){}
public static void registerService(String key, Object instance){
if(!objMap.containsKey(key)){
objMap.put(key, instance);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
********************************************************************************
  将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对应类型的对象。这种方式可以管理多种类型的单利,并且在使用时可以通过统一的接口进行获取操作,减低了用户成本,也隐藏了具体实现,降低了耦合度。

总结,不管是哪种形式的单例模式,他们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在获取过程中必须保证线程安全,防止反序列化导致重新生成实例对象等问题。

七。总结
单例模式的优缺点分析:
优点:客户端可以使用单例模式类的实例的时候,只需要调用一个单一的方法即可生成一个唯一的实例,又利于节约资源
缺点:首先,单例模式很难实现序列化,这就导致采用单例模式的类很难被持久化,当然也很难通过网络传输;其次由于单例模式采用静态方法,无法在继承结构中使用。最后,如果在分布式集群的环境忠存在多个java虚拟机的情况下,具体确定哪个单例再运行也是很困难的事情。
单例模式的实际应用:
单例模式一般会出现在以下情况下:
在多线程之间,共享一个资源或操作同一个对象
在整个程序空间使用全局变量,共享资源
大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

温馨提示:java的线程工作顺序是不确定的,这就导致在多线程的情况没有实例化就使用的情况,进而导致程序奔溃。





























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值