设计模式好好学三(单例模式)

设计模式好好学三(单例模式)

大纲

  • 饿汉式单例

  • 懒汉式单例

  • 懒汉式(双重检测锁模式)

  • 破环单例模式

  • 防止破环单例模式

        单例模式属于创建型的设计模式,无需实例化对象,同时可以确保只有单个对象被创建。

特点如下:

  • 单例类只能有一个实例

  • 单例类必须自己创建自己的唯一实例(构造器私有)

  • 单例类必须给所有其他对象提供这一实例

优点:

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

  • 避免对资源的多重占用(比如写文件操作)

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

饿汉式

饿汉式,在类加载时完成初始化。代码示例如下:

public class Singleton {

    // 直接初始化对象
    private final static Singleton single = new Singleton();  
    // 构造器私有化,无法现示的创建对象。
    private Singleton() {}    

    public static Singleton getInstance() {
        return single;
    }
}


饿汉式避免了多线程的同步问题,但是不管后面用不用得上,在类加载时直接初始化,
没有达到Lazy Loading (懒加载) 的效果,极有可能造成内存浪费

懒汉式

懒汉式就是在类加载时先不初始化,等到第一次被使用时才初始化。

  • 线程不安全
// 线程不安全
public class Singleton {

    private static Singleton single;

    private Singleton() {}

    public static Singleton getInstance() {
        if(single==null) {    //不为null说明已经初始化过了,不需要再进行初始化
            single = new Singleton();
        }
        return single;
    }
}

        懒汉式不会造成内存浪费。代码在单线程下没有问题,但是在多线程下极有可能出现问题。

        类加载是有顺序的,主要分为 加载 —> 连接 —> 初始化。假设对象还没被实例化,然后有两个线程同时访问,那么就可能出现多次实例化的结果。

  • 线程安全
public class Singleton {

    private static Singleton single;

    private Singleton() {}
    // 加锁,双重判空
    public static Singleton getInstance() {
        if(single==null) {
            synchronized (Singleton.class) {
                if(single == null)
                    single = new Singleton();
            }    
        }
        return single;
    }    
}

        在实例化前加锁,防止并发情况下出现多次实例化,进行if(single== null)两次判空检查,可以保证线程安全,实例化代码一次,虽然保证了线程安全,但是又会出现另一个问题:不能保证new过程的原子性操作

new过程主要有三步:

  • 1、分配内存空间

  • 2、执行构造方法、初始化对象

  • 3、把对象指向空间

        正常的顺序是123,但是也有可能出现132的情况,就是还没初始化先指向内存空间。这时候如果有两个线程同时访问,第一个线程不会有问题,会造成第二个线程查询到 single 不为空,直接返回 single,但是此时 single 还没有完成初始化,是个空对象,会出现问题。这个解决办法就是加上 volatile 关键字。

public class Singleton {
    // 加上volatile关键字,保证按照123顺序执行,使其成为原子性操作
    private volatile static Singleton single;

    private Singleton() {}

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

破环单例模式

public class Singleton {
    // 加上volatile关键字,保证按照123顺序执行,使其成为原子性操作
    private volatile static Singleton single;

    private Singleton() {}

    public static Singleton getInstance() {
        if(single==null) {
            synchronized (Singleton.class) {
                if(single == null)
                    single = new Singleton();
            }    
        }
        return 
    }
}
public void main (String args[]) {
    Singleton instance1 = Singleton.getInstance();
    // 获取空参构造器,调用无参构造方法
    Constructor<singleton> deConstructor = Singleton.class.getDeclaredConstructor(null);
    // 破坏私有构造器,无视私有化
    deConstructor.setAccessible(true);
    // 通过反射创建对象
    Singleton instance2 = deConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}
结果会发现两个示例的地址不一样。

防止破坏单例模式

  • 在构造函数中加锁
```java
public class Singleton {
    // 加上volatile关键字,保证按照123顺序执行,使其成为原子性操作
    private volatile static Singleton single;

    private Singleton() {
        synchronized (Singleton.class) {
            if(single!=null) {
                throw new RuntimeException("不要试图使用反射破坏单例模式");
            }

        }
    }

    public static Singleton getInstance() {
        if(single==null) {
            synchronized (Singleton.class) {
                if(single == null)
                    single = new Singleton();
            }    
        }
        return 
    }
}
public void main (String args[]) {
    Singleton instance1 = Singleton.getInstance();
    // 获取空参构造器,调用无参构造方法
    Constructor<singleton> deConstructor = Singleton.class.getDeclaredConstructor(null);
    // 破坏私有构造器,无视私有化
    deConstructor.setAccessible(true);
    // 通过反射创建对象
    Singleton instance2 = deConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}
结果会发现两个示例的地址不一样。
  • 枚举创建单例
public class Singleton {

}

public enum SingletonEnum {
    INSTANCE;

    private Singleton single = null;
    private SingletonEnum () {
        single = new Singleton ();
    }
    public Singleton getSingleton() {
        return single;
    }
}

        枚举实现是在实例化时是线程安全, Java规范中规定,每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。

在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。

end
  • 作者:旭仔(联系作者)
  • 发表时间:2024-03-12 22:05
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论