创建型模式:单例模式

来源:互联网 发布:淘宝店哪家符咒是真的 编辑:程序博客网 时间:2024/04/28 06:08
经过很多大神的总结,目前Java中一共23种经典的设计模式


按照目的,设计模式可以分为以下三种用途:

1.创建型模式:用来处理对象的创建过程

2.结构型模式:用来处理类或者对象的组合

3.行为型模式:用来对类或对象怎样交互和怎样分配职责进行描述


创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:
 工厂方法模式(Factory Method Pattern)
 抽象工厂模式(Abstract Factory Pattern)
 建造者模式(Builder Pattern)
 原型模式(Prototype Pattern)
 单例模式(Singleton Pattern)


结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:
 适配器模式(Adapter Pattern)
 桥接模式(Bridge Pattern)
 组合模式(Composite Pattern)
 装饰者模式(Decorator Pattern)
 外观模式(Facade Pattern)
 享元模式(Flyweight Pattern)
 代理模式(Proxy Pattern)


行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:
 责任链模式(Chain of Responsibility Pattern)
 命令模式(Command Pattern)
 解释器模式(Interpreter Pattern)
 迭代器模式(Iterator Pattern)
 中介者模式(Mediator Pattern)
 备忘录模式(Memento Pattern)
 观察者模式(Observer Pattern)
 状态模式(State Pattern)
 策略模式(Strategy Pattern)
 模板方法模式(Template Method Pattern)
 访问者模式(Visitor Pattern) 


本节主要讲解单例模式(Singleton Pattern)又叫单态模式


单例模式有以下特点:

  1、单例类只能有一个实例。

  2、单例类必须自己创建自己的唯一实例。

  3、单例类必须给所有其他对象提供这一实例。

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。


单例模式的使用场景:

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。

例如:

每台计算机可以有若干个打印机,但只能有一个Printer Spooler【打印机要工作正常,您需要确保机器上的Printer Spooler 这个服务处于运行状态。如果您的Printer Spooler服务没有开启,可以在服务中手动开启Printer Spooler】,以避免两个打印作业同时输出到同一个打印机中。


方法一:饿汉模式

1.饿汉模式单例(线程安全)

public class Singleton1 {

//将自身的实例对象设置为一个属性,并加上static和final修饰符,这样就能保证内存中只有一个对象

private static final Singleton1 instance = new Singleton1();

//把构造方法设置为私有化,这样其他类就不能通过构造方法创建实例了

private Singleton1() {

}

//通过这个静态方法向外界提供这个类的实例,而且这个实例在一开始就已经创建了

public static Singleton1 getInstance() {

return instance;

}

}

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


2.饿汉变种单例(线程安全)

public class Singleton1 {
private Singleton1 instance = null;
static {
instance = new Singleton1 ();
}
private Singleton1 (){

}
public static Singleton1 getInstance() {
return this.instance;
}
}

实际上和饿汉模式是一样的!只是看起来不太一样!


方法二:懒汉模式

1.真正的懒汉模式(线程不安全)

public class Singleton2 {

//在第一次调用的时候实例化自己 

private static Singleton2 instance2 = null;

private Singleton() {} //避免被外部访问

//静态工厂方法,要加上同步

public static Singleton2 getInstance() {

if (instance2 == null)

instance2 = new Singleton2();

return instance2;

}

}


2.懒汉模式(解决线程安全问题)

public class Singleton2 {

//在第一次调用的时候实例化自己 

private static Singleton2 instance2 = null;

private Singleton() {} //避免被外部访问

//静态工厂方法,要加上同步

public staticsynchronized Singleton2 getInstance() {

if (instance2 == null)

instance2 = new Singleton2();

return instance2;

}

}


3.懒汉模式中的双重检查锁定(线程安全)

public class Singleton2 {

//在第一次调用的时候实例化自己 

private static Singleton2 instance2 = null;

//避免被外部访问

private Singleton() {

//静态工厂方法,要加上同步

public static Singleton2 getInstance() {

if (instance2 == null) {

        synchronized(Singleton2 .class) {

if (instance2 == null) {

instance2 = new Singleton2 ();

}

}

}

return instance2;

}

}


4.懒汉模式中的静态内部类

public class Singleton2 {    

private static class LazyHolder {    

private static final Singleton2 INSTANCE = new Singleton2 ();    

}    

private Singleton2 (){

}    

public static final Singleton2 getInstance() {    

return LazyHolder.INSTANCE;    

}    

}

第4种比上面2、3都好一些,既实现了线程安全,又避免了同步带来的性能影响。

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,

它跟饿汉模式不同的是(很细微的差别):

饿汉模式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),

而这种方式是Singleton类被装载了,instance不一定被初始化。因为LazyHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载LazyHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton2 类加载时就实例化,因为我不能确保Singleton2 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比饿汉模式就显得很合理。


上面三种懒汉模式的区别:

第2种:在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,性能损耗太大。

第3种:在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。

第4种:利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。


方法三:枚举

public enum Singleton {  

INSTANCE;

public void whateverMethod() {  

}

}  
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊。


饿汉模式和懒汉模式区别

1.初始化上:

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

2、线程安全:

从代码上不难看出饿汉式是线程安全的,可以直接用于多线程而不会出现问题。

懒汉模式(第一种)本身是非线程安全的,正常使用的时候应该加上线程安全的结局方案,

上面(2、3、4)三种方案可以考虑一下,只是上面三种实现在资源加载和性能方面稍微有些区别。

3、资源加载和性能:

饿汉模式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,

在第一次调用时速度也会更快,因为其资源已经初始化完成。

懒汉模式则会延迟加载,第一次使用的时候才会实例化对象,第一次调用时要做初始化,性能上会有些延迟,之后就和饿汉式一样了。


总结:

下面有两个问题需要注意:

1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。

假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。

不管怎样,如果你序列化一个单例类的对象,接下来复原多个单例类对象,那你就会有多个单例类的实例。

对问题1修复的办法是:

private static Class getClass(String classname) throws ClassNotFoundException {     

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     

if(classLoader == null)

classLoader = Singleton.class.getClassLoader();     

return (classLoader.loadClass(classname));

     }     

 }  
 
 
对问题2修复的办法是:
public class Singleton implements java.io.Serializable {     

public static Singleton INSTANCE = new Singleton();

protected Singleton() {      

}     

private Object readResolve() {     

return INSTANCE;     

}    

 }   


 

最后,如果大家有什么不同的意见或者更好的看法欢迎提出来!



0 0