单例模式和多例的区别

设计者模式-单例模式和多例的区别

单例模式的关键有两点:

1、构造方法为私有,这样外界就不能随意调用。
2、get的方法为静态,由类直接调用

多例模式(Multiton)

1 、多例类可以有多个实例
2 、多例类必须能够自我创建并管理自己的实例,并向外界提供自己的实例。

一、单例模式和多例模式说明:

  1. 单例模式和多例模式属于对象模式。
  2. 单例模式的对象在整个系统中只有一份,多例模式可以有多个实例。
  3. 它们都不对外提供构造方法,即构造方法都为私有。

单例和多例的详细描述:

  1. 什么是单例多例:
    所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;
  2. 如何产生单例多例:
    在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope=”prototype”;
  3. 为什么用单例多例:
    1).之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
    2).之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:

当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;
4. 何时用单例?何时用多例?
对于struts2来说,action必须用多例,因为action本身含有请求参数的值,即可改变的状态;
而对于STRUTS1来说,action则可用单例,因为请求参数的值是放在actionForm中,而非action中的;
另外要说一下,并不是说service或dao一定是单例,标准同第3点所讲的,就曾见过有的service中也包含了可改变的状态,同时执行方法也依赖该状态,但一样用的单例,这样就会出现隐藏的BUG,而并发的BUG通常很难重现和查找;

二、单例的写法

第一种(懒汉,线程不安全):
public class Singleton {  
     private static Singleton instance;  
     private Singleton (){}   
     public static Singleton getInstance() {  
     if (instance == null) {  
         instance = new Singleton();  
     }  
     return instance;  
     }  
 }   

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

第二种(懒汉,线程安全):
public class Singleton {
   private static Singleton instance;
   private Singleton (){}
   public static synchronized Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }  
} 

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

第三种(饿汉):
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }  
} 

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第四种(饿汉,变种):
public class Singleton {
    private Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return this.instance;
    }  
} 

表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

第五种(静态内部类):
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
} 

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理

第六种(枚举):
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
} 

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过

第七种(双重校验锁):
public class Singleton {  
    private volatile static Singleton singleton;
    private Singleton (){}   
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }  
} 

这个是第二种方式的升级版,俗称双重检查锁定

总结

有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对第一个问题修复的办法是:
private static Class getClass(String classname) throws ClassNotFoundException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader == null)
        classLoader = Singleton.class.getClassLoader();
    return (classLoader.loadClass(classname));
} 
对第二个问题修复的办法是:
public class Singleton implements java.io.Serializable { 
    public static Singleton INSTANCE = new Singleton();  
    protected Singleton() {
    }  
    private Object readResolve() {
        return INSTANCE;
    }  
}
### 单例模式 (Singleton Pattern) 单例模式是一种创建型设计模式,其目的是确保一个类只有一个实,并提供全局访问点来获取该唯一实。为了实现这一点,通常会将构造函数设为私有,并通过静态方法或属性来控制对象的创建访问。 ```csharp public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() {} } ``` 这种方式可以防止其他代码直接调用构造器而创建新的实[^1]。 ### 多例模式 (Multiton Pattern) 多例模式则是对单例模式的一种扩展,在这种情况下不是只允许存在一实而是允许多个特定键对应的实被管理起来。这意味着对于每一个唯一的标识符(通常是字符串形式),只会有一个关联的对象实;但如果请求不同的键,则可以获得不同但同样遵循此规则的新实。 ```java import java.util.HashMap; public final class Multiton { private static HashMap<String, Multiton> instances = new HashMap<>(); // Private constructor to prevent instantiation. private Multiton(){} /** * Get an instance based on a key value pair. */ public static synchronized Multiton getInstance(String key){ if (!instances.containsKey(key)){ instances.put(key,new Multiton()); } return instances.get(key); } // Other methods... } ``` 上述代码展示了如何利用哈希表存储各个`key-value`关系下的实化对象。 ### 主要差异 - **数量上的区别**: 单例模式严格限制整个应用程序生命周期内仅能拥有某类型的个共享实;相比之下,多例模式则支持按需创建并维护一组有限数量的不同实。 - **灵活性方面**: 使用多例模式可以根据业务逻辑定义更多种类的状态或者配置项作为区分依据,从而使得程序更加灵活适应变化的需求场景。 综上所述,虽然两者都属于控制对象创建的设计思路范畴之内,但是它们之间存在着本质性的差别,这取决于实际应用场景的具体需求来决定采用哪种方式更为合适。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值