设计模式-单例模式(Singleton Pattern)

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

第一种:懒汉模式(线程不安全)

public class Singleton {
    
    private static Singleton instance;
    private Singleton(){}    
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

1.私有的构造函数 , 防止这个类形成多个实例
2.静态方法getInstance()为外部应用提供该实例
上面的这个程序存在比较严重的问题 , 因为是全局性的实例 , 多线程情况下 , 如果多个线程同时调用 getInstance() 方法 ,
可能有多个进程同时通过 instance == null 的条件检查 , 于是多个实例就被创建出来了 , 并且可能造成内存泄露 . 所以对
代码进行更改 , 如下:

public class Singleton {
    
    private static Singleton instance;
    private Singleton(){}    
    
    public static Singleton getInstance(){
        synchronized(Singleton.class){
            if(instance == null){
                instance = new Singleton();
            }
        }        
        return instance;
    }
}

使用JAVA的synchronized关键字 , 如果有多个线程同时操作 , synchronized方法会帮我们同步所有的线程 , 但是 , 效率很低,
大部分情况都不需要去同步 , 只要判断 instance 不是为空了 , 就不需要去同步线程 . 所以有了以下版本:

public class Singleton {
    
    private static Singleton instance;
    private Singleton(){}    
    
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }    
        }            
        return instance;
    }
}

这种俗称双重检查锁定 , Double-Check .
1.第一个条件判断 , 如果实例已经被创建了 , 那没问题 , 直接返回 .
2.不然我们就进行线程同步操作 .
3.第二个条件 , 如果同步线程中有一个线程创建了实例 , 其余的就不需要去创建 .

但是还有一个问题 , instance = new Singleton()并非一个原子操作 , 在JVM中这句话做了三件事情 :
1.给singleton分配内存空间 .
2.调用Singleton的构造函数来初始化成员变量 , 形成实例 .
3.将singleton对象指向分配的内存空间(执行完这一步 , singleton对象才非 null)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,
最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,
这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
对此,我们只需要把singleton声明成 volatile 就可以了 , 下面就是更改后的版本:

public class Singleton {
    
    private volatile static Singleton instance;
    private Singleton(){}    
    
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }    
        }            
        return instance;
    }
}

使用 volatile 有两个功用:

1)这个变量不会在多个线程中存在复本,直接从内存读取。

2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

但是,这个事情仅在Java 1.5版后有用,1.5版之前用这个变量也有问题,因为老版本的Java的内存模型是有缺陷的。

第二种:饿汉模式

public class Singleton {
    
    private volatile static Singleton instance = new Singleton();
    
    private Singleton(){}    
    
    public static Singleton getInstance(){        
        return instance;
    }
}

但是,这种玩法的最大问题是——当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。
于是,这个可能会与我们想要的行为不一样,比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。

好吧 , 还得参照下 <<Effective JAVA>>

第三种: 内部类

public class Singleton {
    
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    
    private Singleton(){}    
    
    public static 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;
}

总结:
有两个问题需要注意:
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;
    }
    
}

 

posted @ 2015-06-16 15:56  SpenserLiu  阅读(86)  评论(0)    收藏  举报