【创建型】- 单例模式

  单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

 

实现方式一:饿汉模式

package com.huang.pims.demo.patterns.singleton;

public class HungerSingleton {

    private static HungerSingleton singleInstance = new HungerSingleton();

    private HungerSingleton() {
        super();
    }

    public static HungerSingleton getInstance() {
        return singleInstance;
    }

}

实现方式二:懒汉模式

package com.huang.pims.demo.patterns.singleton;

public class LazySingleton {

    private static LazySingleton singleInstance;

    private LazySingleton() {
        super();
    }

    public static LazySingleton getInstance() {
        if (null == singleInstance) { // 位置1
            singleInstance = new LazySingleton();
        }
        return singleInstance;
    }

}

  懒汉模式,在单线程环境下,功能是正常的。但是在多线程环境下,就不能保证所有线程通过 LazySingleton.getInstance() 获得的对象是同一个对象了。

  假如:线程A运行到位置1处进行判空,cpu给的时间片用完了,切换到其他线程运行,比如线程B,它也在调用LazySingleton.getInstance(),并成功获取到了单例对象。当线程A再次获得cpu分配的时间片时,由于之前对singleInstance的判空结果为true,线程B会再次创建一个对象实例。这就使得线程A和线程B获得的并不是同一个对象。

  解决方案:使用synchronized关键字

实现方式三:线程安全的懒汉模式

package com.huang.pims.demo.patterns.singleton;

public class SyncMethodSingleton {

    private static SyncMethodSingleton singleInstance;

    private SyncMethodSingleton() {
        super();
    }
  // 使用synchronized关键字修饰方法
    public static synchronized SyncMethodSingleton getInstance() {
        if(null == singleInstance) {
            singleInstance = new SyncMethodSingleton();
        }
        return singleInstance;
    }
    
}

  如此,在多线程环境下,众多线程在调用LazySingleton.getInstance()时,得先尝试获取对象锁,获得锁的线程A才能继续往下执行,方法执行完毕之后线程A才会释放锁。线程A释放对象锁之前,其他调用LazySingleton.getInstance()的线程则全部阻塞。

  这就解决了懒汉模式在多线程环境下线程不安全的问题。

实现方式四:基于双重校验、线程安全的懒汉模式

package com.huang.pims.demo.patterns.singleton;

public class SyncCodeSingleton {

    private static SyncCodeSingleton singletonInstance;

    private SyncCodeSingleton() {
        super();
    }

    public static SyncCodeSingleton getInstance() {
        if(null == singletonInstance) {// 第一步判空,不需要获得锁
            synchronized (SyncCodeSingleton.class) {
                if(null == singletonInstance) {// 第二步判空,判断当前线程获得锁之前,是否已经有其他线程获取锁并实例化对象了
                    singletonInstance = new SyncCodeSingleton();
                }
            }
        }
        return singletonInstance;
    }
    
}

  此种实现方式有一个缺陷:指令重排序可能导致返回的是一个没有实例化的对象。 

  singletonInstance = new VolatileSingleton(); 在代码层面,这只是一行代码,但是JVM在执行该行代码的时候,会分三步执行

  1.   分配内存
  2.   使用SyncCodeSingleton的构造函数来初始化实例对象
  3.   将singletonInstance指向分配的内存空间(执行完这步 singletonInstance 就不再为 null 了)

  JVM为了提高自身的效率,可能会进行指令重排序,互换第二步和第三步的执行顺序。

  假设线程A运行到singletonInstance = new VolatileSingleton();时,刚执行完第一步和第三步,还没来得及初始化实例对象,而此时的singletonInstance已然不是null了。这时,如果有线程B运行到首次判空的地方,判断结果自然为false,那么线程B就会返回一个没有初始化的singletonInstance,从而导致获取单例对象失败。

  解决方案:使用volatile关键字修饰singletonInstance,禁止了指令重排序,从而保证,如果singletonInstance不为null,singletonInstance必然已经经过了初始化。

package com.huang.pims.demo.patterns.singleton;

public class VolatileSingleton {
   // 使用volatile关键字禁止指令重排序
    private static volatile VolatileSingleton singletonInstance;

    private VolatileSingleton() {
        super();
    }

    public static VolatileSingleton getInstance() {
        if (null == singletonInstance) {
            synchronized (SyncCodeSingleton.class) {
                if (null == singletonInstance) {
                    singletonInstance = new VolatileSingleton();
                }
            }
        }
        return singletonInstance;
    }

}

实现方式五:基于类初始化来实现单例模式

package com.huang.pims.demo.patterns.singleton;

public class InnerInitSingleton {

    private static class InnerClass {
        private static InnerInitSingleton singletonInstance = new InnerInitSingleton();
    }

    private InnerInitSingleton() {
        super();
    }

    public static InnerInitSingleton getInstance() {
        return InnerClass.singletonInstance;
    }

}

  该种模式也有一种缺陷,就是可以使用Java反射机制来new出一个新的单例对象。

实现方式六:基于枚举类实现单例模式

public enum  EnumSingleton {

SINGLETON_INSTANCE("hello");

private String prop;

EnumSingleton(String prop) {
this.prop = prop;
}
  // 省略setter、getter方法

public static EnumSingleton getInstance() {
return SINGLETON_INSTANCE;
}

}

  基于枚举类实现的单例模式,无法使用Java反射来生成一个新的实例对象。使用Java反射是会抛出Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects异常


 

posted @ 2019-05-16 15:30  狱婪  阅读(169)  评论(0编辑  收藏  举报