码迷,mamicode.com
首页 > 其他好文 > 详细

设计模式之单例设计模式

时间:2018-06-08 22:11:11      阅读:206      评论:0      收藏:0      [点我收藏+]

标签:str   懒汉式   ret   单例   增加   ade   final   临界区   系统   

设计模式之单例设计模式

  单例模式的实现目标就是保证一个类有且仅有一个实例,当然这也是有前提的,就是由同一个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() {
        
    }
}

 

设计模式之单例设计模式

标签:str   懒汉式   ret   单例   增加   ade   final   临界区   系统   

原文地址:https://www.cnblogs.com/gdy1993/p/9157458.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!