设计模式——单例模式

单例模式是什么?

单例模式确保一个类只有一个实例,并提供一个全局访问点。

单例模式解决什么问题?

创建全局变量(类加载才会创建),全局变量也可以保证实例只有一个:

public static int GLOBAL_VARIABLE;

全局变量创建时需要赋值,且无法延迟加载

公有域

将域声明为公有静态常量会破坏封装特性

class Singleton {
    public static final Singleton singleton = new Singleton();

    private Singleton() {

    }
}

懒汉式

创建私有静态变量,私有化构造函数,暴露公有方法返回

public class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

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

当两个线程同时进入if (singleton == null)时,就会产生两个实例

懒汉加锁式

给实现1的getInstance()方法加上synchronized,即可解决产生两个实例的问题:

public class Singleton {
    private static Singleton singleton;

    private Singleton() {

    }

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

加锁降低性能,且只有在第一次执行getInstance()时才需要同步,之后执行应该要直接返回

饿汉式

取消延迟加载,在创建变量时直接初始化,可避免实现1和2的问题,但会提前占用资源

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

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

双重校验锁(推荐)

使用双重检查加锁机制,将变量声明为volatile,首先检查是否为空,为空则对类加锁,加锁后再次检查是否为空,为空则创建

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {

    }

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

可以实现延迟加载和多线程同步,但在某些情况下(JDK1.5之前)也会失效

静态内部类(推荐)

class Singleton {
    private Singleton() {

    }

    private static class SingletonHolder {
        private static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
}

可以实现延迟加载和多线程同步,但(上述所有)可以被反射和反序列化破坏

打破单例及处理

以饿汉式为例

class Singleton implements Serializable {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

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

利用反射

Singleton singleton = Singleton.getInstance();
Class cls = Singleton.class;
try {
    Constructor constructor = cls.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton otherSingleton = (Singleton) constructor.newInstance();
    System.out.println(singleton);
    System.out.println(otherSingleton);
} catch (Exception e) {
    e.printStackTrace();
}

如上,通过反射开放构造函数,如下打印的地址不一致

com.demo.demo0.Singleton@ad6e6b0
com.demo.demo0.Singleton@3ea0929

要解决上述问题,可在构造函数中添加判断,当创建第二个实例时抛出异常

class Singleton implements Serializable {
    private static Singleton singleton = new Singleton();

    private Singleton() {
        if (singleton != null) {
            throw new RuntimeException();
        }
    }

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

利用反序列化

Singleton singleton = Singleton.getInstance();
try {
    FileOutputStream fileOutputStream = openFileOutput("data", Context.MODE_PRIVATE);
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(singleton);
    objectOutputStream.flush();
    objectOutputStream.close();
    FileInputStream fileInputStream = openFileInput("data");
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    Singleton otherSingleton = (Singleton) objectInputStream.readObject();
    System.out.println(singleton);
    System.out.println(otherSingleton);
} catch (Exception e) {
    e.printStackTrace();
}

如上,利用序列化写到文件,反序列化读取时会产生新的实例,如下打印的地址不一致

com.demo.demo0.Singleton@82694ae
com.demo.demo0.Singleton@64e824f

解决办法是将单例的所有域声明为transient,并提供一个readResolve返回唯一的实例

class Singleton implements Serializable {
    private transient static Singleton singleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }

    private Object readResolve() throws ObjectStreamException {
        return singleton;
    }
}

枚举(最优)

不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,也阻止了通过反射调用私有构造方法

public enum Singleton {  
    INSTANCE; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值