单例模式中的懒汉模式,饿汉模式 , 双检锁/双重校验锁

单例模式

单例模式是java中最简单的设置模式之一,属于创建型设计模式,它提供了一种创建对象最佳方法. 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象方式,可以直接访问,不需要实例化该类的对象;

单例总结

1, 单例类只有一个实例;
2, 单例类必须自己创建自己的唯一实例;
3, 单例类必须给所有其他对象提供这一实例;

确保一个类仅有一个实例化,提供全局唯一访问点,主要解决了一个全局的类频繁的创建与销毁 ; 减少不必要的资源浪费;

关键代码

构造函数是私有的

使用单例模式优点

内存中一个类只有一个实例,减少了内存开销,尤其是频繁的创建和销毁实例;

缺点

没有接口,不能继承, 与单一职责原则冲突,只关心内部逻辑,不关心外面怎么实例化;

单例模式集中实现方式

饿汉式

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

饿汉式在类加载时就实例化对了,使用时直接调用 getInstance() 方法。这个模式为线程安全的,在多线程并发模式下不会重复实例化对象。
优点: 效率高
缺点: 对象实例化过早,浪费内存资源

懒汉模式

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }

 public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

这种模式没有在加载时实例化对象,而是在调用getInstance() 方法时实例化对象,使用懒汉式是为了避免过早的实例化,减少内存资源浪费。
优点:第一次调用才初始化,避免内存浪费。
缺点: 只适合单线程,线程不安全

改进1 、懒汉式–引入synchronized


public class Singleton {
    private static Singleton singleton;

    private Singleton() {
    }
    // 添加了synchronized 关键字,保证线程安全
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

引入synchronized 关键字修饰getInstance() 方法,是为了解决线程不安全问题。利用多线程同步机制,然原先的线程不安全回归到线程安全。
优点:第一次调用才初始化,避免内存浪费。
缺点:引入synchronized 会因为线程阻塞、切换会带一些不必要的开销,从而降低了系统性能,效率会降低。

改进2、双检锁/双重校验锁

public class Singleton {
    private static volatile Singleton singleton;

    private Singleton() {
    }

     public static Singleton getSingleton() {
        if (singleton == null) {
            // 使用类锁
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
 }

对比改进1 ,可以看到synchronized不在修饰一个方法,而是缩小到修饰代码块,因为同步锁的范围越小,性能影响就越小,效率越高

这里可以注意到修饰变量instance的关键字增加了volatile。这里volatile主要作用是提供内存屏障,禁止指令重排序。

现有t1、t2两个线程同时访问getInstance(),假设t1、t2都执行到A处。由于有同步锁,只能有个1个线程获得锁,假如t1拥有该同步锁,t1执行到C处instace = new Singleton()。将会做如下3步骤:
1.分配内存空间
2.初始化
3.将instance指向分配内存空间
正常的执行顺序应为:1->2->3。执行第3步时,这时候的instance就不再是null了。但由于指令重排序的存在,执行顺序有可能变化为:1->3->2。当执行3的时候,instance就不再是null,但初始化工作有可能还没有做完。这时候如果t2获取锁执行的话,就会直接获取有可能还没有初始化完成的instance。这样使用instance会引起程序报错。当然这也是极端情况下,我尝试几次无法捕捉重现,但并不意味着问题不存在。volatile当然还是要加的。

A处if判断作用主要是防止过多是线程执行同步代码块;如果是单例模式的话,这里同步代码块只会被执行一次。B处if判断作用主要是防止多线程作用下重复实例化,保证线程安全。这也被称为:双重检查锁定。
双重检查锁定属于一种兼顾线程安全和性能的实现。

改进3、静态内部类

public class Singleton {
    private Singleton(){}
    private static class Holder {
        public static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.instance; // 执行Holder的初始化工作
    }
}

使用静态内部类也是懒汉模式的一种实现,当调用ggetInstance()才会触发加载静态内部类,从而初始化获取instance实例。利用静态内部类的加载机制来保证线程安全。

枚举

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

这种方式简洁,制动支持序列化机制,防止对此实例化,是实现单例模式最佳方法
用枚举方式实现单例模式,是目前比较推荐的。枚举方式的好处是:1、线程安全;2、防止反射出现多个实例;3、防止反序列化出现多个实例。

PS: 部分参考 :https://juejin.im/post/58c2c6b10ce4630054655a1d

你可能感兴趣的:(设计模式)