单例设计模式
概念理解
单例设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
该设计模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,并且由这个类提供一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
单例设计模式分为:懒汉式和饿汉式。
饿汉式:类加载会导致该单实例对象被创建。
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建。
饿汉式
静态变量方式
public class Singleton {
private Singleton(){};
private static Singleton singleton = new Singleton();
public static Singleton getSingleton(){
return singleton;
}
}
静态代码块方式
public class Singleton {
private Singleton(){};
private static Singleton instance;
static {
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
枚举方式
枚举类型是线程安全的,并且只会装载一次。枚举类型是所有单例实现中唯一一种不会被破坏的单例实现模式。
public enum Singleton {
INSTANCE;
}
懒汉式
线程不安全方式
public class Singleton {
private Singleton(){};
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
线程安全方式
在 getInstance() 方法上添加synchronized
关键字,解决线程安全问题,但是导致该方法的执行效果特别低。
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
双重检查锁方式
解决了单例、性能、线程安全问题,但是在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
为解决双重检查锁模式带来的空指针异常问题,只需要使用volatile
关键字,volatile
关键字可以保证可见性和有序性。能够保证在多线程的情况下线程安全也不会有性能问题。
public class Singleton {
private Singleton(){}
private static Singleton instance;
//private static volatile Singleton instance;
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类方式
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
静态内部类方式保证了多线程下的安全,并没有任何性能影响和空间的浪费。
public class Singleton {
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
存在问题与解决方案
除了枚举方式,如果将Singleton序列化或者反射,则可以创建多个对象。
序列化问题
下面代码演示表明,序列化和反序列化已经破坏了单例模式。
public class Singleton implements Serializable {
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
public class SerializableQuestion {
public static void main(String[] args) throws Exception {
//write();
Singleton singleton1 = read();
Singleton singleton2 = read();
System.out.println(singleton1 == singleton2);
//输出结果为false
}
public static Singleton read() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\upload\\object.txt"));
Singleton instance = (Singleton) ois.readObject();
return instance;
}
public static void write() throws IOException {
Singleton instance = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\upload\\object.txt"));
oos.writeObject(instance);
oos.close();
}
}
序列化问题解决
在Singleton类中添加readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable {
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
private Object readResolve(){
return SingletonHolder.INSTANCE;
}
}
反射问题
public class Singleton {
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
public class ReflectionQuestion {
public static void main(String[] args) throws Exception {
//获取Singleton的字节码对象
Class singletonClass = Singleton.class;
//获取无参构造函数
Constructor cons = singletonClass.getDeclaredConstructor();
//取消访问检查
cons.setAccessible(true);
Singleton s1 = (Singleton) cons.newInstance();
Singleton s2 = (Singleton) cons.newInstance();
System.out.println(s1 == s2);
}
反射问题解决
调用构造方法进行创建时,直接抛出异常。
public class Singleton {
private static boolean flag = false;
private Singleton(){
//非第一次访问
if(flag){
throw new RuntimeException("不可以创建多个对象!");
}
flag = true;
}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}