单例模式的实现(学习笔记)

单例模式的实现

单例模式:

定义:采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(一般是静态方法)

单例模式的八种方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(“线程安全”,同步代码块)
  6. 双重锁
  7. 静态内部类
  8. 枚举法
饿汉式(静态常量)

实例代码

public class Singleton {
    //使用final修饰是防止被继承
    private static final Singleton instance= new Singleton();
    //构造器私有化 外部不能new对象    
    private Singleton(){}
    //提供一个静态的公开方法,当使用该方法时,才去创建instance
    public static Singleton getInstance(){
        return instance;
    }
}

步骤:

  1. 构造器私有化 (私有化的目的是:防止new一个对象实例)
  2. 类的内部创建对象(在类加载的时候创建一个对象实例instance)
  3. 向外暴露一个静态的公共方法返回一个实例对象(getInstance()方法去获得一个实例)
  4. 代码实现
饿汉式(静态常量)的优缺点:
  1. 优点:写法简单,在类装载时就完成实例化。避免线程同步问题。
  2. 缺点:在类装载时就完成了实例化,没有达到Lazy Loading(懒加载)的效果。如果从来没有使用这个实例就会造成内存的浪费。
  3. 方法基于classloder机制避免多线程的同步问题,在这种方法instance在类加载的时候就完成了实例化,在单例模式中大多数是调用getInstance方法。但是导致类加载的原因有很多种,不确定有其他的方式(或其他的静态方法导致类加载),这样初始化就没有达到Lazy Loading懒加载的效果,如果还没使用该实例对象更造成了内存的浪费。
饿汉式(静态代码块)

实例代码

public class Singleton {
    //构造器私有化 外部不能new对象    
    private Singleton(){}
    //在本类内部创建对象实例
    private static Singleton instance;
    static{
        //在静态代码块中实例对象
        instance=new Singleton();
    }
    public static Singleton getInstance(){
        return instance;
    }
}
饿汉式(静态代码块)的优缺点:

与饿汉式静态常量的优缺点类似,也是在类加载的时候执行动态代码块中的代码。

懒汉式(线程不安全)

实例代码

public class Singleton2 {
        //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
        private static Singleton2 instance;
        //构造器私有化
        private Singleton2(){}
        //提供一个静态的公开方法,当使用该方法时,才去创建instance
        public static Singleton2 getInstance(){
            if(instance==null){
                instance=new Singleton2();
            }
            return instance;
        }
懒汉式(线程不安全)的优缺点:
  1. 起到了懒加载Lazy Loading的效果,但是只能在单线程下使用
  2. 如果在多线程下,一个线程进入了if(instance==null)判断语句块,还未来的及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
懒汉式(线程安全,同步方法)

实例代码

public class Singleton2 {
        //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
        private static Singleton2 instance;
        //构造器私有化
        private Singleton2(){}
        //提供一个静态的公开方法,加入了同步处理的代码,解决线程安全问题
        public static synchronized Singleton2 getInstance(){
            if(instance==null){
                instance=new Singleton2();
            }
            return instance;
        }
懒汉式(线程安全,同步方法)的优缺点:
  1. 解决了线程不安全问题
  2. 效率太低,每个线程想要获取类的实例时候,执行getInstance()方法都需要进行同步。而其实这个方法只需要执行一次实例化代码就行,后面想要获得该类实例,直接return就行。
懒汉式(“线程安全”,同步代码块)

代码实例

public class Singleton2 {
        //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
        private static Singleton2 instance;
        //构造器私有化
        private Singleton2(){}
        //提供一个静态的公开方法,
        public static Singleton2 getInstance(){
            if(instance==null){
                //本意时在判断后进行方法同步
                synchronized(Singleton2.class){
                singleton=new Singleton2();
                }
            }
            return singleton;
        }
懒汉式(“线程安全”,同步代码块)优缺点:
  1. 本意是对懒汉式线程安全的改进,改为同步产生实例化的代码块
  2. 但是这种同步并不能起到线程同步的作用。这种方法的缺点和第3类类似,多线程同时进入if判断时,会导致多个实例的创建导致无法达到线程安全。
双重锁(DoubleCheck)

实例代码

public class Singleton3 {
    //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
    private volatile static Singleton3 Singleton3;
	//构造器私有化
    private Singleton3() {
    }
	//提供一个静态的公开方法,加入了双重检查的代码,解决线程安全问题,解决懒加载问题
    public static Singleton3 newInstance() {
        if (Singleton3 == null) {
            synchronized (Singleton3.class) {
                if (Singleton3 == null) {
                    Singleton3 = new Singleton3();
                }
            }
        }
        return Singleton3;
    }
}

双重锁(DoubleCheck)优缺点:
  1. 双重锁概念多线程开发经常使用,两次if判断保证线程安全

  2. 这样实例化代码只执行一次,避免反复进行方法同步

  3. 线程安全,延迟加载,效率较高

  4. 但是也是有问题的Singleton3 = new Singleton3();这句操作并非一个原子操作。事实上在 JVM 中这句话大概做了下面 3 件事情。

    1.给 instance 分配内存
    2.调用 Singleton 的构造函数来初始化成员变量
    3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
    我们只需要将 instance 变量声明成 volatile 就可以了

5.有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。

静态内部类

代码实例

public class Singleton4 {
    //构造器私有化
    private Singleton4(){}
    //写一个静态内部类,该类中有一个静态属性Singleton4
    private static class SingletonClassInstance{
        private static final Singleton4 INSTANCE=new Singleton4();
    }
	//提供一个静态方法,直接返回SingletonClassInstance.INSTANCE
    public static Singleton4 getInstance(){
        return SingletonClassInstance.INSTANCE;
    }
}
静态内部类优缺点:
  1. 这种方式采用了类加载的机制来保证初始化
  2. 静态内部类方法在Singleton类被加载时并不会立即实例化,而是在需要实例化时,调用getInstance方法才会装载SingletonClassInstance内部类,从而实现Singleton的实例化
  3. 类的静态属性只会在第一次加载时初始化一次,所以在这里JVM帮助我们保证了线程的安全性,在类进行初始化时别的线程是无法进入的
  4. 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
枚举

实例代码

public enum  Singleton4 {
    //枚举元素本身就是单例
            INSTANCE;
    //添加自己需要的操作
    public void singletonOperation(){
    }
    //测试
     public static void main(String[] args) {
        Singleton4 instance=Singleton4.INSTANCE;
        Singleton4 instance2=Singleton4.INSTANCE;
        System.out.println(instance==instance2);    
    }
}
枚举优缺点:
  1. 借助JDK1.5中的枚举实现单例模式,不仅可以避免多线程的问题,还可以防止反序列化重新创建对象。
  2. 我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值