什么是单例模式
单例模式是23种设计模式中的一种,属于创建型模式,这里简单说一下什么是创建型模式
创建型模式用于描述『怎样创建对象』,它的主要特点是将对象的创建与使用分离。包含单例、原型、工厂方 法、抽象工厂、建造者 5 种创建型模式。
单例模式的优点的优缺点
优点
- 对于频繁使用的对象,可以节省内存,加快对象访问速度,可以省略 new 操作花费的时间。
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停 顿时间。
饿汉式的优缺点
- 优点:实现简单,不需要加锁,直接在类加载的时候被创建,执行效率高。
- 线程安全:由于在类加载时就创建单例对象,因此不存在多线程环境下的同步问题。
- 缺点:类加载时就初始化,浪费内存(即使这个单例没有用到也会被创建)。饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。
- 应用场景:适合单例占用内存比较小,在初始化时就会被用到的情况。如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
懒汉式的优缺点
- 优点:单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象,避免内存浪费。
- 缺点:相较于饿汉式更复杂一点,首次创建时可能需要加锁。
- 应用场景:如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。
饿汉式单例模式
public class HungryManStyle implements Serializable {
/**
* 静态变量(单例对象),类加载时就初始化对象(不存在线程安全问题)
*/
private static final HungryManStyle INSTANCE = new HungryManStyle();
private HungryManStyle() {
//保证反射不能创建对象
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
}
public HungryManStyle getInstance() {
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
枚举饿汉式单例模式
public enum EnumerateHungryStyle {
INSTANCE;
public void method(){
//在这里实现单例对象的功能
}
}
- 底层会自动在静态代码块中让INSTANCE=new Singleton2("INSTTANCE",0); 并且INSTANCE是private static final的。
- 枚举没有无参构造,并且底层不允许反射调用其构造方法,如果调用会抛出异常。
- 枚举饿汉式能天然防止反射、反序列化破坏单例。虽然这种方法还没有广泛采用,但是单元素的枚举类型经常成为实现单例的最佳方法。
- 如果单例必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口)。
懒汉式单例模式
/**
* 饿汉式单例
* 优点:
* 线程安全:由于在类加载时就创建单例对象,因此不存在多线程环境下的同步问题。
* 没有加锁的性能问题:饿汉式没有使用同步锁,因此不存在加锁带来的性能问题。
* 实现简单:饿汉式的实现比较简单,不需要考虑多线程环境下的同步问题。
* 缺点:
* 立即加载:由于在类加载时就创建单例对象,因此可能会影响程序的启动速度。
* 浪费资源:如果单例对象很大,并且程序中很少使用,那么饿汉式可能会浪费资源。
*/
public class HungryStyle implements Serializable {
/**
* 静态变量(单例对象),类加载时就初始化对象(不存在线程安全问题)
*/
private static final HungryStyle INSTANCE = new HungryStyle();
private HungryStyle() {
//保证反射不能创建对象
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
}
public HungryStyle getInstance() {
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
双重检查懒汉式
public class DoubleLockStyle {
private DoubleLockStyle() {
}
/**
* volatile关键字,使得instance变量在多个线程间可见,禁止指令重排序优化
* volatile是一个轻量级的同步机制,即轻量锁
*/
private static volatile DoubleLockStyle INSTANCE = null;
public static DoubleLockStyle getInstance() {
//避免多个线程在已经又单例对象的情况下,来争抢锁资源
if (INSTANCE == null) {
//加锁是为了保证多线程情况下只创建一个对象
synchronized (DoubleLockStyle.class) {
if (INSTANCE == null) {
//INSTANCE = new DoubleLockStyle();不是原子的,包括对象实例化,执行构造方法,给静态变量赋值,后面两步可能会被指令重排
INSTANCE = new DoubleLockStyle();
}
}
}
return INSTANCE;
}
}