设计模式之单例设计模式

设计模式之单例设计模式

  单例模式的实现目标就是保证一个类有且仅有一个实例,当然这也是有前提的,就是由同一个ClassLoader加载的这个类有且仅有一个对象,如果这里类由不同的ClassLoader加载,则会产生多个对象。

  (一) 单线程下的单例设计模式

  (1)饿汉式  

public class Singleton {
    private static final Singleton instance = new Singleton();
    //构造函数私有化
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        return instance;
    }

}

  (2)懒汉式

public class Singleton {
    private static Singleton instance = null;
    //构造函数私有化
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        if(null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

  (二)多线程下的单例设计模式

  在多线程环境下,饿汉式单例模式是线程安全的。但懒汉式单例模式getInstance()的if语句形成了一个if-then-act操作,它并不是一个原子操作,因此可能引发线程安全问题,创建多个对象,破坏单例性。

  很容易让我们想到的是,采用同步(synchronized)的方式加锁,保证线程安全,如下所示:

/**
 * 基于简单加锁的单例模式实现
 * @author Administrator
 *
 */
public class Singleton {
    private static Singleton instance = null;
    //构造函数私有化
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        synchronized(Singleton.class) {
            if(instance == null) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

  这种方法的单例模式固然是线程安全的,但是每个线程在执行getInstance()方法时,都必须申请锁,增加了系统的开销,降低了效率,而我们需要的仅仅是第一次创建对象时加锁即可,之后所有线程可并发获得该对象。于是我们采用双重检查锁定的方式实现单例模式:

/**
 * 基于双重检查锁定的单例模式实现(错误代码)
 * @author Administrator
 *
 */
public class Singleton {
    private static Singleton instance = null;
    //构造函数私有化
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请
            synchronized(Singleton.class) {
                if(instance == null) {//第二次判断用来避免创建多个对象
                    instance = new Singleton();//语句①
                }
            }
        }
        return instance;
    }
}

  看似这种做法很巧妙解决了多线程环境下的单例模式的安全问题。其实不然,上述代码中的语句①可分解为三个子操作,分配对象所需的内存空间(子操作①)、初始化对象(子操作②)和将对象引用传入栈内的变量。想象一下这样一个场景,线程t1通过了第一次null == instance的判断,线程t1持有锁进行了第二次判断,此时线程t1进入了临界区,根据临界区重排序规则:临界区内的代码运行被重排序,因此,子操作③可能排在了子操作②之前,因此当线程t1执行到操作③时,由于此时操作②并没有执行,因此instance对象并没有初始化完成,但是instance以不为空,就在此时,线程t2执行了第一次null==instance判断,随然instance并未初始化完成,但以不是 null,所以直接返回给线程t2 instance对象,这样线程t2调用instance对象时就可能产生未知的错误。所以上述代码是不正确的。

  需要将instance变量的采用volatile修饰,禁止其写操作与该操作之前的任何读、写操作进行重排序,因此,用volatile修饰instance相当于禁止了子操作②(对对象进行初始化的写操作)重拍到子操作③(对对象引用写入共享变量的子操作)之前,保证了线程读到的instance都是初始化完成的instance。

/**
 * 基于双重检查锁定的单例模式实现(正确实现)
 * @author Administrator
 *
 */
public class Singleton {
    private static volatile Singleton instance = null;//保障有序性
    //构造函数私有化
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        if(null == instance) {//第一次判断用于判断对象是否被已经初始化,当对象已经初始化时,直接返回对象,避免锁的申请
            synchronized(Singleton.class) {
                if(instance == null) {//第二次判断用来避免创建多个对象
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  考虑到双重检测锁定法实现上容易出错,我们选择另外一种可以延迟加载的单例模式:基于静态内部类的单例模式。

  静态内部类只有在初次访问时才会触发虚拟机对该类进行初始化,因此SingletonInstanceHolder.INSTANCE会初始化静态内部类 

/**
 * 基于静态内部类的单例模式实现
 * @author Administrator
 *
 */
public class Singleton {
    //构造函数私有化
    private Singleton() {
        
    }
    
    private static class SingletonInstanceHolder {
        private final static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInstanceHolder.INSTANCE;
    }
}

  也可通过枚举来实现线程安全的单例模式:

public enum Singleton {
    INSTANCE;//为单例对象
    
    Singleton() {
        
    }
    
    public void doSomething() {
        
    }
}

 

posted @ 2018-06-08 20:13  sowhat1943  阅读(112)  评论(0编辑  收藏  举报